├── Core ├── Source │ ├── default.css.c │ ├── NSString+UTF8Cleaner.h │ ├── UIFont+DTCoreText.h │ ├── NSMutableString+HTML.h │ ├── DTTextBlock.m │ ├── UIFont+DTCoreText.m │ ├── DTCoreTextFontCollection.h │ ├── NSAttributedStringRunDelegates.h │ ├── NSScanner+HTML.h │ ├── DTTextBlock.h │ ├── DTWebVideoView.h │ ├── DTAttributedTextCell.h │ ├── NSString+Paragraphs.h │ ├── DTImage+HTML.m │ ├── DTLazyImageView.h │ ├── NSMutableString+HTML.m │ ├── DTImage+HTML.h │ ├── DTAttributedTextView.h │ ├── CGUtils.h │ ├── NSAttributedString+SmallCaps.h │ ├── DTCoreTextLayouter.h │ ├── NSMutableAttributedString+HTML.h │ ├── DTCompatibility.h │ ├── DTLinkButton.h │ ├── NSString+Paragraphs.m │ ├── DTCoreTextGlyphRun.h │ ├── NSAttributedStringRunDelegates.m │ ├── DTCoreText.h │ ├── DTHTMLAttributedStringBuilder.h │ ├── NSAttributedString+HTML.m │ ├── DTCSSStylesheet.h │ ├── DTCoreTextConstants.h │ ├── NSAttributedString+HTML.h │ ├── DTCoreTextFontDescriptor.h │ ├── DTCoreTextConstants.m │ ├── NSCharacterSet+HTML.h │ ├── NSString+CSS.h │ ├── NSCharacterSet+HTML.m │ ├── NSAttributedString+SmallCaps.m │ ├── DTCoreTextLayoutLine.h │ ├── NSMutableAttributedString+HTML.m │ ├── NSString+HTML.h │ ├── DTHTMLElement.h │ ├── DTWebVideoView.m │ ├── DTTextAttachment.h │ ├── CGUtils.m │ ├── NSAttributedString+DTCoreText.h │ ├── DTCSSListStyle.h │ ├── DTAttributedTextCell.m │ ├── DTCoreTextLayouter.m │ ├── default.css │ ├── DTCoreTextFontCollection.m │ ├── DTColor+HTML.h │ ├── DTAttributedTextContentView.h │ ├── DTAttributedTextView.m │ ├── DTCoreTextParagraphStyle.h │ └── DTCoreTextGlyphRun.m ├── Test │ ├── Resources │ │ ├── PreWhitespace.html │ │ ├── MalformedURL.html │ │ ├── CustomFont.plist │ │ ├── Video.plist │ │ ├── Empty_and_Unclosed_Paragraphs.html │ │ ├── WarAndPeace.plist │ │ ├── ListTest.plist │ │ ├── PreWhitespace.plist │ │ ├── Emoji.html │ │ └── NavTag.html │ ├── Source │ │ ├── NSStringHTMLTest.h │ │ ├── NSStringHTMLTest.m │ │ ├── NSAttributedStringHTMLTest.h │ │ ├── UIColorHTMLTest.h │ │ ├── NSString+SlashEscaping.h │ │ ├── MacUnitTest.h │ │ ├── NSString+SlashEscaping.m │ │ ├── UIColorHTMLTest.m │ │ └── NSAttributedStringHTMLTest.m │ ├── UnitTest-Prefix.pch │ ├── MacUnitTest-Prefix.pch │ ├── UnitTest-Info.plist │ └── MacUnitTest-Info.plist ├── DTCoreText-Prefix.pch └── DTCoreText-Info.plist ├── Demo ├── Resources │ ├── EmojiTest.html │ ├── Icon.png │ ├── Icon@2x.png │ ├── Oliver.jpg │ ├── icon_smile.gif │ ├── XB Niloofar.ttf │ ├── XB NiloofarBd.ttf │ ├── Default-568h@2x.png │ ├── ArabicTest.html │ ├── CurrentTest.html │ ├── Subviews.html │ ├── CoreTextIssues.html │ ├── Video.html │ ├── Objects.html │ ├── APOD.html │ ├── TextBoxes.html │ ├── LineHeight.html │ ├── Alignment.html │ ├── README.html │ ├── Image.html │ ├── styles.html │ ├── iOS6.html │ ├── Snippets.plist │ ├── CustomFont.html │ └── ListTest.html ├── DemoApp-Prefix.pch ├── Source │ ├── DemoSnippetsViewController.h │ ├── main.m │ ├── CoreTextDemoAppDelegate.h │ ├── DemoTextViewController.h │ ├── CoreTextDemoAppDelegate.m │ └── DemoSnippetsViewController.m └── DemoApp-Info.plist ├── .gitignore ├── .gitmodules ├── DTCoreText.podspec ├── LICENSE └── Readme.markdown /Core/Source/default.css.c: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Demo/Resources/EmojiTest.html: -------------------------------------------------------------------------------- 1 | Here is a Smiley: 😄 and Mr. Poop: 💩 -------------------------------------------------------------------------------- /Demo/Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamma/DTCoreText/master/Demo/Resources/Icon.png -------------------------------------------------------------------------------- /Demo/Resources/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamma/DTCoreText/master/Demo/Resources/Icon@2x.png -------------------------------------------------------------------------------- /Demo/Resources/Oliver.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamma/DTCoreText/master/Demo/Resources/Oliver.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | *.mode1v3 4 | *.pbxuser 5 | project.xcworkspace 6 | xcuserdata 7 | .svn 8 | -------------------------------------------------------------------------------- /Demo/Resources/icon_smile.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamma/DTCoreText/master/Demo/Resources/icon_smile.gif -------------------------------------------------------------------------------- /Demo/Resources/XB Niloofar.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamma/DTCoreText/master/Demo/Resources/XB Niloofar.ttf -------------------------------------------------------------------------------- /Demo/Resources/XB NiloofarBd.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamma/DTCoreText/master/Demo/Resources/XB NiloofarBd.ttf -------------------------------------------------------------------------------- /Demo/Resources/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamma/DTCoreText/master/Demo/Resources/Default-568h@2x.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Core/Externals/DTFoundation"] 2 | path = Core/Externals/DTFoundation 3 | url = git://github.com/Cocoanetics/DTFoundation.git 4 | -------------------------------------------------------------------------------- /Core/Test/Resources/PreWhitespace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

some text

4 |
5 | 	
6 | 	source code     123
7 | 	123 123
8 | 
9 |

more text

-------------------------------------------------------------------------------- /Core/Test/Resources/MalformedURL.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Forgot Password? -------------------------------------------------------------------------------- /Demo/Resources/ArabicTest.html: -------------------------------------------------------------------------------- 1 |

بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ

2 |

بسم الله الرحمن الرحيم

3 | -------------------------------------------------------------------------------- /Core/Test/Resources/CustomFont.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IgnoreCase 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Core/Test/Resources/Video.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SkipUnitTest 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Core/Test/Resources/Empty_and_Unclosed_Paragraphs.html: -------------------------------------------------------------------------------- 1 | 2 |

Next paragraph empty

3 |

4 |

Next paragraph missing closing (auto-closed, resulting in one empty paragraph)

5 |

Fin

-------------------------------------------------------------------------------- /Core/Test/Resources/WarAndPeace.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SkipUnitTest 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Core/Test/Source/NSStringHTMLTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSStringHTMLTest.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Claus Broch on 11/01/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | @interface NSStringHTMLTest : SenTestCase { 10 | 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Test/Resources/ListTest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IgnoreNonAlphanumericCharacters 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Core/Test/Resources/PreWhitespace.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IgnoreNonAlphanumericCharacters 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Core/Test/Source/NSStringHTMLTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSStringHTMLTest.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Claus Broch on 11/01/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "NSStringHTMLTest.h" 10 | 11 | @implementation NSStringHTMLTest 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Test/Resources/Emoji.html: -------------------------------------------------------------------------------- 1 | 2 |

FYI emoji work just fine, simply encode them as hex strings. For 3 | example, 🍁 (🍁) correctly 4 | displays a maple leaf. Even double-character emoji like 5 | 🇰🇷 (🇰 🇷) display 6 | correctly.

-------------------------------------------------------------------------------- /Core/Test/Source/NSAttributedStringHTMLTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedStringHTMLTest.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Claus Broch on 11/01/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | @interface NSAttributedStringHTMLTest : SenTestCase { 10 | 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Test/UnitTest-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'UnitTest' target in the 'CoreTextExtensions' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /Demo/DemoApp-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'CoreTextExtensions' target in the 'CoreTextExtensions' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | 7 | #import 8 | #import 9 | #import 10 | 11 | #import "DTCoreText.h" 12 | 13 | #endif -------------------------------------------------------------------------------- /Core/Test/Resources/NavTag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Core/Test/Source/UIColorHTMLTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColorHTMLTest.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Claus Broch on 11/01/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | @interface UIColorHTMLTest : SenTestCase { 10 | 11 | } 12 | 13 | - (void) testValidColorWithHexString; 14 | - (void) testColorHTMLHexString; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Core/Test/Source/NSString+SlashEscaping.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+SlashEscaping.h 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 02.02.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSString (SlashEscaping) 12 | 13 | - (NSString *)stringByAddingSlashEscapes; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Core/Source/NSString+UTF8Cleaner.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+UTF8Cleaner.h 3 | // CoreTextExtensions 4 | // 5 | // Created by John Engelhart on 6/26/11. 6 | // Copyright 2011 Scribd Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSString (MalformedUTF8Additions) 12 | - (id)initWithPotentiallyMalformedUTF8Data:(NSData *)data; 13 | @end 14 | -------------------------------------------------------------------------------- /Demo/Source/DemoSnippetsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoSnippetsViewController.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Sam Soffes on 1/14/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | @interface DemoSnippetsViewController : UITableViewController { 10 | 11 | NSArray *_snippets; 12 | 13 | NSCache *cellCache; 14 | } 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Core/Test/MacUnitTest-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'MacUnitTest' target in the 'MacUnitTest' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #import 9 | #import "DTCoreText.h" 10 | #endif 11 | 12 | #define CGSizeValue sizeValue 13 | #define valueWithCGSize valueWithSize 14 | -------------------------------------------------------------------------------- /Core/DTCoreText-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the Static Library and Framework targets in the 'DTCoreText' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | 7 | #import 8 | #import 9 | 10 | #if TARGET_OS_IPHONE 11 | #import 12 | #elif TARGET_OS_MAC 13 | #import 14 | #endif 15 | 16 | #endif -------------------------------------------------------------------------------- /Demo/Source/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/9/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | int main(int argc, char *argv[]) { 10 | 11 | @autoreleasepool { 12 | int retVal = UIApplicationMain(argc, argv, nil, @"CoreTextDemoAppDelegate"); 13 | return retVal; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Core/Test/Source/MacUnitTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // MacUnitTest.h 3 | // MacUnitTest 4 | // 5 | // Created by Oliver Drobnik on 22.01.12. 6 | // Copyright (c) 2012 Drobnik KG. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MacUnitTest : SenTestCase 12 | 13 | - (void)internalTestCaseWithURL:(NSURL *)URL withTempPath:(NSString *)tempPath; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Core/Source/UIFont+DTCoreText.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+DTCoreText.h 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 11.12.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | @interface UIFont (DTCoreText) 10 | 11 | /** 12 | Creates a UIFont that matches the provided CTFont. 13 | @param ctFont a `CTFontRef` 14 | @returns The matching UIFont 15 | */ 16 | + (UIFont *)fontWithCTFont:(CTFontRef)ctFont; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Demo/Resources/CurrentTest.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa 3 |
      4 |
    • bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb
    • 5 |
    6 |
  • 7 |
8 | -------------------------------------------------------------------------------- /Core/Source/NSMutableString+HTML.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableString+HTML.h 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 01.02.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | 10 | /** 11 | Categories needed for modifying mutable strings, as needed for DTCoreText. 12 | */ 13 | @interface NSMutableString (HTML) 14 | 15 | /** 16 | Removes the trailing whitespace from the receiver. 17 | */ 18 | - (void)removeTrailingWhitespace; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Demo/Source/CoreTextDemoAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppDelegate.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/9/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | @class DemoTextViewController; 10 | 11 | @interface CoreTextDemoAppDelegate : NSObject 12 | { 13 | UIWindow *_window; 14 | UINavigationController *_navigationController; 15 | } 16 | 17 | @property (nonatomic, strong) UIWindow *window; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Core/Source/DTTextBlock.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTTextBlock.m 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 04.03.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "DTTextBlock.h" 10 | #import "DTCoreText.h" 11 | 12 | @implementation DTTextBlock 13 | { 14 | DTEdgeInsets _padding; 15 | DTColor *_backgroundColor; 16 | } 17 | 18 | #pragma mark Properties 19 | 20 | @synthesize padding = _padding; 21 | @synthesize backgroundColor = _backgroundColor; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Core/Source/UIFont+DTCoreText.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+DTCoreText.m 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 11.12.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "UIFont+DTCoreText.h" 10 | 11 | @implementation UIFont (DTCoreText) 12 | 13 | + (UIFont *)fontWithCTFont:(CTFontRef)ctFont 14 | { 15 | NSString *fontName = (__bridge NSString *)CTFontCopyName(ctFont, kCTFontPostScriptNameKey); 16 | CGFloat fontSize = CTFontGetSize(ctFont); 17 | return [UIFont fontWithName:fontName size:fontSize]; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Demo/Source/DemoTextViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoTextViewController.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/9/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "DTAttributedTextView.h" 10 | #import "DTLazyImageView.h" 11 | 12 | @interface DemoTextViewController : UIViewController 13 | 14 | @property (nonatomic, strong) NSString *fileName; 15 | 16 | @property (nonatomic, strong) NSURL *lastActionLink; 17 | 18 | @property (nonatomic, strong) NSURL *baseURL; 19 | 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Core/Source/DTCoreTextFontCollection.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTCoreTextFontCollection.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 5/23/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | 10 | 11 | @class DTCoreTextFontDescriptor; 12 | 13 | 14 | @interface DTCoreTextFontCollection : NSObject 15 | 16 | + (DTCoreTextFontCollection *)availableFontsCollection; 17 | 18 | - (id)initWithAvailableFonts; 19 | 20 | - (NSArray *)fontFamilyNames; 21 | - (NSArray *)fontDescriptors; 22 | 23 | - (DTCoreTextFontDescriptor *)matchingFontDescriptorForFontDescriptor:(DTCoreTextFontDescriptor *)descriptor; 24 | 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Core/Source/NSAttributedStringRunDelegates.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedStringRunDelegates.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver on 14.01.11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #if TARGET_OS_IPHONE 10 | #import 11 | #elif TARGET_OS_MAC 12 | #import 13 | #endif 14 | 15 | void embeddedObjectDeallocCallback(void *context); 16 | CGFloat embeddedObjectGetAscentCallback(void *context); 17 | CGFloat embeddedObjectGetDescentCallback(void *context); 18 | CGFloat embeddedObjectGetWidthCallback(void *context); 19 | CTRunDelegateRef createEmbeddedObjectRunDelegate(id obj); 20 | -------------------------------------------------------------------------------- /Demo/Resources/Subviews.html: -------------------------------------------------------------------------------- 1 |

Custom Subviews

2 |

Hyperlinks are implemented via attaching a custom button over glyph runs that make up a hyperlink. Multiple glyph runs in the same line are merged for one single subview. A hyperlink wrapping over several lines are multiple buttons which are linked via a GUID so that they highlight together

3 |

Here's an example involving multiple gylph runs resulting from Chinese characters:
4 | Here are some chinese Glyphs #草原牧马# 草原 making up a hyperlink that also wraps over two lines bla bla bla bla bla bla bla bla bla bla bla bla

5 |

Follow @cocoanetics on Twitter

6 | -------------------------------------------------------------------------------- /Core/Source/NSScanner+HTML.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSScanner+HTML.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/12/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | //#import "DTColor+HTML.h" 10 | 11 | @class DTColor; 12 | 13 | @interface NSScanner (HTML) 14 | 15 | - (BOOL)scanHTMLTag:(NSString **)tagName attributes:(NSDictionary **)attributes isOpen:(BOOL *)isOpen isClosed:(BOOL *)isClosed; 16 | - (BOOL)scanDOCTYPE:(NSString **)contents; 17 | - (BOOL)scanCSSAttribute:(NSString **)name value:(NSString **)value; 18 | - (BOOL)scanCSSURL:(NSString **)urlString; 19 | 20 | - (BOOL)scanHTMLColor:(DTColor **)color; 21 | 22 | - (void)logPosition; 23 | 24 | @end 25 | 26 | -------------------------------------------------------------------------------- /Core/Source/DTTextBlock.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTTextBlock.h 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 04.03.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "DTCompatibility.h" 10 | 11 | /** 12 | Class that represents a block of text with attributes like padding or a background color. 13 | */ 14 | @interface DTTextBlock : NSObject 15 | 16 | /** 17 | The space to be applied between the layouted text and the edges of the receiver 18 | */ 19 | @property (nonatomic, assign) DTEdgeInsets padding; 20 | 21 | 22 | /** 23 | The background color to paint behind the text in the receiver 24 | */ 25 | @property (nonatomic, strong) DTColor *backgroundColor; 26 | 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Core/Source/DTWebVideoView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTWebVideoView.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 8/5/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | 10 | 11 | @class DTTextAttachment; 12 | @class DTWebVideoView; 13 | 14 | @protocol DTWebVideoViewDelegate 15 | 16 | @optional 17 | - (BOOL)videoView:(DTWebVideoView *)videoView shouldOpenExternalURL:(NSURL *)url; 18 | 19 | @end 20 | 21 | 22 | @interface DTWebVideoView : UIView 23 | 24 | @property (nonatomic, assign) id delegate; // subtle simulator bug - use assign not __unsafe_unretained 25 | @property (nonatomic, strong) DTTextAttachment *attachment; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Core/Test/UnitTest-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.yourcompany.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleSignature 16 | ???? 17 | CFBundleVersion 18 | 1.0 19 | 20 | 21 | -------------------------------------------------------------------------------- /Core/Source/DTAttributedTextCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTAttributedTextCell.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 8/4/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | @class DTAttributedTextContentView; 10 | 11 | @interface DTAttributedTextCell : UITableViewCell 12 | 13 | @property (nonatomic, strong) NSAttributedString *attributedString; 14 | @property (nonatomic, readonly) DTAttributedTextContentView *attributedTextContextView; 15 | 16 | - (id)initWithReuseIdentifier:(NSString *)reuseIdentifier accessoryType:(UITableViewCellAccessoryType)accessoryType; 17 | 18 | - (void)setHTMLString:(NSString *)html; 19 | 20 | - (CGFloat)requiredRowHeightInTableView:(UITableView *)tableView; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Core/Test/MacUnitTest-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.drobnik.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Core/Source/NSString+Paragraphs.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Paragraphs.h 3 | // DTRichTextEditor 4 | // 5 | // Created by Oliver Drobnik on 11/11/11. 6 | // Copyright (c) 2011 Cocoanetics. All rights reserved. 7 | // 8 | 9 | 10 | /** 11 | Methods simplifying dealing with text that is in paragraphs. 12 | 13 | The character used to separate paragraphs from each other is '\n'. 14 | */ 15 | @interface NSString (Paragraphs) 16 | 17 | 18 | /* 19 | Extends the given range such that it contains only full paragraphs. 20 | */ 21 | - (NSRange)rangeOfParagraphsContainingRange:(NSRange)range parBegIndex:(NSUInteger *)parBegIndex parEndIndex:(NSUInteger *)parEndIndex; 22 | 23 | 24 | - (BOOL)indexIsAtBeginningOfParagraph:(NSUInteger)index; 25 | 26 | - (NSRange)rangeOfParagraphAtIndex:(NSUInteger)index; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Core/Source/DTImage+HTML.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTImage+HTML.m 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 31.01.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "DTImage+HTML.h" 10 | 11 | #if TARGET_OS_IPHONE 12 | 13 | @implementation UIImage (HTML) 14 | 15 | - (NSData *)dataForPNGRepresentation 16 | { 17 | return UIImagePNGRepresentation(self); 18 | } 19 | 20 | @end 21 | 22 | #else 23 | 24 | @implementation NSImage (HTML) 25 | 26 | - (NSData *)dataForPNGRepresentation 27 | { 28 | [self lockFocus]; 29 | NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0, 0, self.size.width, self.size.height)]; 30 | [self unlockFocus]; 31 | 32 | return [bitmapRep representationUsingType:NSPNGFileType properties:Nil]; 33 | } 34 | 35 | @end 36 | 37 | #endif -------------------------------------------------------------------------------- /Core/Source/DTLazyImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTLazyImageView.h 3 | // PagingTextScroller 4 | // 5 | // Created by Oliver Drobnik on 5/20/11. 6 | // Copyright 2011 . All rights reserved. 7 | // 8 | 9 | 10 | #import 11 | 12 | @class DTLazyImageView; 13 | 14 | @protocol DTLazyImageViewDelegate 15 | @optional 16 | - (void)lazyImageView:(DTLazyImageView *)lazyImageView didChangeImageSize:(CGSize)size; 17 | @end 18 | 19 | @interface DTLazyImageView : UIImageView 20 | @property (nonatomic, strong) NSURL *url; 21 | @property (nonatomic, strong) NSMutableURLRequest *urlRequest; 22 | @property (nonatomic, assign) BOOL shouldShowProgressiveDownload; 23 | 24 | @property (nonatomic, assign) id delegate; // subtle simulator bug - use assign not __unsafe_unretained 25 | 26 | - (void)cancelLoading; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Core/Source/NSMutableString+HTML.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableString+HTML.m 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 01.02.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "NSMutableString+HTML.h" 10 | 11 | 12 | #define IS_WHITESPACE(_c) (_c == ' ' || _c == '\t' || _c == 0xA || _c == 0xB || _c == 0xC || _c == 0xD || _c == 0x85) 13 | 14 | @implementation NSMutableString (HTML) 15 | 16 | - (void)removeTrailingWhitespace 17 | { 18 | NSUInteger length = self.length; 19 | 20 | NSInteger lastIndex = length-1; 21 | NSInteger index = lastIndex; 22 | NSInteger whitespaceLength = 0; 23 | 24 | while (index>=0 && IS_WHITESPACE([self characterAtIndex:index])) 25 | { 26 | index--; 27 | whitespaceLength++; 28 | } 29 | 30 | // do the removal once for all whitespace characters 31 | if (whitespaceLength) 32 | { 33 | [self deleteCharactersInRange:NSMakeRange(index+1, whitespaceLength)]; 34 | } 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Core/DTCoreText-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.drobnik.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Demo/Resources/CoreTextIssues.html: -------------------------------------------------------------------------------- 1 |

Core Text Bugs

2 |

Bold for Chinese Glyphs

3 |

This is a bug that was present before iOS 6, the Chinese glyphs in the following line would be bold if the locale was set to Chinese due to an incorrect entry in the global font cascade table. Filed as rdar://11262229

4 |

English and 中文 -- Chinese characters

5 | 6 |

Italic for Chinese Glyphs in Fallback

7 |

东方盖饭

8 |

东方盖饭

9 | 10 |

Extra space above lines with whitespace glyphs

11 |

Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla

12 |

Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla Bla bla

-------------------------------------------------------------------------------- /Demo/Source/CoreTextDemoAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoAppDelegate.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/9/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "CoreTextDemoAppDelegate.h" 10 | #import "DemoSnippetsViewController.h" 11 | 12 | @implementation CoreTextDemoAppDelegate 13 | 14 | @synthesize window = _window; 15 | 16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 17 | { 18 | // Create window 19 | _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 20 | 21 | // Create the view controller 22 | DemoSnippetsViewController *snippetsViewController = [[DemoSnippetsViewController alloc] init]; 23 | _navigationController = [[UINavigationController alloc] initWithRootViewController:snippetsViewController]; 24 | 25 | // Display the window 26 | _window.rootViewController = _navigationController; 27 | [_window makeKeyAndVisible]; 28 | 29 | return YES; 30 | } 31 | 32 | 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Core/Source/DTImage+HTML.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTImage+HTML.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/9/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #if TARGET_OS_IPHONE 10 | 11 | /** 12 | Category used to have the same method available for unit testing on Mac on iOS. 13 | */ 14 | @interface UIImage (HTML) 15 | 16 | /** 17 | Retrieve the NSData representation of a UIImage. Used to encode UIImages in DTTextAttachments. 18 | 19 | @returns The NSData representation of the UIImage instance receiving this message. Convenience method for UIImagePNGRepresentation(). 20 | */ 21 | - (NSData *)dataForPNGRepresentation; 22 | 23 | @end 24 | 25 | #else 26 | 27 | /** 28 | Category used to have the same method available for unit testing on Mac on iOS. 29 | */ 30 | @interface NSImage (HTML) 31 | 32 | 33 | /** 34 | Retrieve the NSData representation of a NSImage. 35 | 36 | @returns The NSData representation of the NSImage instance receiving this message. 37 | */ 38 | - (NSData *)dataForPNGRepresentation; 39 | 40 | @end 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /Demo/DemoApp-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleDisplayName 8 | Rich Text 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | com.drobnik.DTCoreText.demo 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | ${PRODUCT_NAME} 19 | CFBundlePackageType 20 | APPL 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIAppFonts 28 | 29 | XB NiloofarBd.ttf 30 | XB Niloofar.ttf 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Core/Source/DTAttributedTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTAttributedTextView.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/12/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "DTAttributedTextContentView.h" 10 | 11 | @class DTAttributedTextView; 12 | 13 | @interface DTAttributedTextView : UIScrollView 14 | { 15 | DTAttributedTextContentView *contentView; 16 | UIView *backgroundView; 17 | } 18 | 19 | @property (nonatomic, strong) NSAttributedString *attributedString; 20 | 21 | @property (nonatomic, strong, readonly) DTAttributedTextContentView *contentView; 22 | @property (nonatomic, strong) IBOutlet UIView *backgroundView; 23 | 24 | @property (nonatomic, unsafe_unretained) IBOutlet id textDelegate; 25 | 26 | 27 | /** 28 | Scrolls the receiver to the anchor with the given name to the top. 29 | @param anchorName The name of the href anchor. 30 | @param animated `YES` if the movement should be animated. 31 | */ 32 | - (void)scrollToAnchorNamed:(NSString *)anchorName animated:(BOOL)animated; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Core/Source/CGUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // CGUtils.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/16/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | //see implementation file for note on deprecation 10 | //CGPathRef newPathForRoundedRect(CGRect rect, CGFloat cornerRadius, BOOL roundTopCorners, BOOL roundBottomCorners); 11 | 12 | /** 13 | Determines the new zoom only computing if the sizeToFit is smaller than the originalSize. The zoom scale is computed by whichever resizing scale along the X or Y is smaller preserving the aspect ratio by respecting the axis with more room. The new size is then computed by multipliying the originalSize by that zoom scale. 14 | @returns New size that fits the sizeToFit while still preserving the aspect ratio of the originalSize. 15 | */ 16 | CGSize sizeThatFitsKeepingAspectRatio2(CGSize originalSize, CGSize sizeToFit); 17 | 18 | /** 19 | Convenience method to find the center of a CGRect. Uses CGRectGetMidX and CGRectGetMidY. 20 | @returns The point which is the center of rect. 21 | */ 22 | CGPoint CGRectCenter(CGRect rect); 23 | -------------------------------------------------------------------------------- /Core/Source/NSAttributedString+SmallCaps.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+SmallCaps.h 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 31.01.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | /** 10 | Methods that generated an attributed string with Small Caps, even if the used fonts don't support them natively. 11 | 12 | This category works equally for Mac and iOS attributed strings. 13 | */ 14 | 15 | @interface NSAttributedString (SmallCaps) 16 | 17 | /** 18 | Creates an `NSAttributedString` from the given text and attributes and synthesizes small caps. On iPad there is only one font that has native small caps, for all other fonts the small caps are synthesized by reduzing the font size for all lowercase characters. 19 | 20 | @param text The string to convert into an attributed string 21 | @param attributes A dictionary with attributes for the attributed string 22 | @returns An attributed string with synthesized small caps. 23 | */ 24 | + (NSAttributedString *)synthesizedSmallCapsAttributedStringWithText:(NSString *)text attributes:(NSDictionary *)attributes; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Core/Source/DTCoreTextLayouter.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTCoreTextLayouter.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/24/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | 10 | 11 | #if TARGET_OS_IPHONE 12 | #import 13 | #elif TARGET_OS_MAC 14 | #import 15 | #endif 16 | 17 | #import "DTCoreTextLayoutFrame.h" 18 | #import "DTCoreTextLayoutLine.h" 19 | #import "DTCoreTextGlyphRun.h" 20 | 21 | 22 | @interface DTCoreTextLayouter : NSObject 23 | 24 | - (id)initWithAttributedString:(NSAttributedString *)attributedString; 25 | 26 | - (CGSize)suggestedFrameSizeToFitEntireStringConstraintedToWidth:(CGFloat)width; 27 | 28 | - (NSInteger)numberOfFrames; 29 | - (void)addTextFrameWithFrame:(CGRect)frame; 30 | 31 | - (DTCoreTextLayoutFrame *)layoutFrameWithRect:(CGRect)frame range:(NSRange)range; 32 | 33 | - (DTCoreTextLayoutFrame *)layoutFrameAtIndex:(NSInteger)index; 34 | 35 | @property (nonatomic, strong) NSAttributedString *attributedString; 36 | 37 | @property (nonatomic, readonly) CTFramesetterRef framesetter; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Demo/Resources/Video.html: -------------------------------------------------------------------------------- 1 |

Video

2 |

HTML5 Video

3 |

This demonstrates support for the HTML5 VIDEO (once it gets implemented)

4 | 7 | 8 | 9 |

To Do

10 |
    11 |
  • Add Rotation when video in fullscreen - there's a bug causing the navigation bar to shift under the status bar if Rotation is enabled
  • 12 |
  • Support for the different kinds of attributes to map to properties of media player
  • 13 |
  • Hide text contained in recognized tags
  • 14 |
15 | 16 |

YouTube in IFRAME

17 | -------------------------------------------------------------------------------- /Demo/Resources/Objects.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 |

Custom Objects

10 |

This demonstrates usage of the <object> tag for embedding your own views

11 |

12 | or

13 |

These views are writting in HTML like this:

14 |
<object style="display:inline;margin-bottom:1em;" someColorParameter="red" width=100 height=20></object>
15 |

You provide your own custom view in the -attributedTextContentView:viewForAttachment:frame: method.

16 |
if (attachment.contentType == DTTextAttachmentTypeObject)
17 | {
18 |   // somecolorparameter has a HTML color
19 |   UIColor *someColor = [UIColor colorWithHTMLName:[attachment.attributes objectForKey:@"somecolorparameter"]];
20 | 
21 |   UIView *someView = [[UIView alloc] initWithFrame:frame];
22 |   someView.backgroundColor = someColor;
23 |   someView.layer.borderWidth = 1;
24 |   someView.layer.borderColor = [UIColor blackColor].CGColor;
25 | 
26 |   return someView;
27 | }
28 | -------------------------------------------------------------------------------- /DTCoreText.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'DTCoreText' 3 | spec.version = '1.0.2' 4 | spec.platform = :ios 5 | spec.license = 'BSD' 6 | spec.source = { :git => 'https://github.com/Cocoanetics/DTCoreText.git', :tag => '1.0.2' } 7 | spec.source_files = 'Core/Source/*.{h,m,c}' 8 | spec.frameworks = 'MediaPlayer', 'QuartzCore', 'CoreText', 'CoreGraphics', 'ImageIO' 9 | spec.requires_arc = true 10 | spec.homepage = 'https://github.com/Cocoanetics/DTCoreText' 11 | spec.summary = 'Methods to allow using HTML code with CoreText.' 12 | spec.author = { 'Oliver Drobnik' => 'oliver@drobnik.com' } 13 | spec.library = 'xml2' 14 | spec.xcconfig = { 'HEADER_SEARCH_PATHS' => '"$(SDKROOT)/usr/include/libxml2"' } 15 | def spec.post_install(target) 16 | prefix_header = config.project_pods_root + target.prefix_header_filename 17 | prefix_header.open('a') do |file| 18 | file.puts(%{#ifdef __OBJC__\n#import \n#endif\n#define ALLOW_IPHONE_SPECIAL_CASES 1}) 19 | end 20 | Dir.chdir(config.project_pods_root + 'DTCoreText/Core/Source/') do 21 | Dir.glob('*.css') do |css_file| 22 | system '/usr/bin/xxd', '-i', css_file, css_file + '.c' 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /Core/Source/NSMutableAttributedString+HTML.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableAttributedString+HTML.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 4/14/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | 10 | 11 | @class DTCoreTextParagraphStyle, DTCoreTextFontDescriptor; 12 | 13 | /** 14 | Methods for appending `NSString` instances to mutable attributed strings 15 | */ 16 | @interface NSMutableAttributedString (HTML) 17 | 18 | /** 19 | Appends a string with the same attributes as this string to this string. 20 | @param string The string to be appended to this string. */ 21 | - (void)appendString:(NSString *)string; 22 | 23 | /** 24 | Appends a string with a given paragraph style and font to this string. 25 | @param string The string to be appended to this string. 26 | @param paragraphStyle Paragraph style to be attributed to the appended string. 27 | @param fontDescriptor Font descriptor to be attributed to the appended string. */ 28 | - (void)appendString:(NSString *)string withParagraphStyle:(DTCoreTextParagraphStyle *)paragraphStyle fontDescriptor:(DTCoreTextFontDescriptor *)fontDescriptor; 29 | 30 | /** 31 | Appends a string without any attributes. 32 | @param string The string to be appended to this string without any attributes. 33 | */ 34 | - (void)appendNakedString:(NSString *)string; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /Demo/Resources/APOD.html: -------------------------------------------------------------------------------- 1 |

Newline Spaces and BaseURL Handling

2 |

This HTML has text beginning after ending tags on new lines. These need to turn into spaces. Also the HTML links are relative links, this tests if they get turned into links relative to the supplied BaseURL

3 |

The remarkable view 5 | follows the locations of this galaxy's 6 | once and future stars. 8 | 9 | In reddish hues, image data from the large 10 | Herschel infrared 11 | observatory traces enormous lanes of dust, 12 | warmed by stars, sweeping along Andromeda's spiral arms. 13 | 14 | The dust, in conjunction with the galaxy's interstellar gas, 15 | comprises the raw material for future 16 | star formation. 17 | 18 | X-ray data from the XMM-Newton 19 | observatory in blue 20 | pinpoint Andromeda's X-ray binary 21 | star systems. 22 | 23 | These systems likely contain neutron stars or stellar mass 24 | black holes that represent final stages in stellar evolution. 25 | 26 | More than twice the size of our own Milky Way, 27 | the Andromeda Galaxy is over 200,000 light-years across.

-------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Oliver Drobnik All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | - Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | - Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /Core/Source/DTCompatibility.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTCompatibility.h 3 | // DTCoreText 4 | // 5 | // Created by Oliver Letterer on 09.04.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | // DTColor is UIColor on iOS, NSColor on Mac 10 | #if TARGET_OS_IPHONE 11 | @compatibility_alias DTColor UIColor; 12 | #else 13 | @compatibility_alias DTColor NSColor; 14 | #endif 15 | 16 | // DTImage is UIImage on iOS, NSImage on Mac 17 | #if TARGET_OS_IPHONE 18 | @compatibility_alias DTImage UIImage; 19 | #else 20 | @compatibility_alias DTImage NSImage; 21 | #endif 22 | 23 | // DTEdgeInsets is UIEdgeInsets on iOS, NSEdgeInsets on Mac 24 | #if TARGET_OS_IPHONE 25 | #define DTEdgeInsets UIEdgeInsets 26 | #define DTEdgeInsetsMake(a, b, c, d) UIEdgeInsetsMake(a, b, c, d) 27 | #else 28 | #define DTEdgeInsets NSEdgeInsets 29 | #define DTEdgeInsetsMake(a, b, c, d) NSEdgeInsetsMake(a, b, c, d) 30 | 31 | // These may be out of place here. Feel free to move them! 32 | // Sourced from https://github.com/andrep/RMModelObject 33 | static inline NSString* NSStringFromCGRect(const CGRect rect) 34 | { 35 | return NSStringFromRect(NSRectFromCGRect(rect)); 36 | } 37 | 38 | static inline NSString* NSStringFromCGSize(const CGSize size) 39 | { 40 | return NSStringFromSize(NSSizeFromCGSize(size)); 41 | } 42 | 43 | static inline NSString* NSStringFromCGPoint(const CGPoint point) 44 | { 45 | return NSStringFromPoint(NSPointFromCGPoint(point)); 46 | } 47 | #endif 48 | -------------------------------------------------------------------------------- /Core/Source/DTLinkButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTLinkButton.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/16/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | /** 10 | Constant for highlighting notification 11 | */ 12 | 13 | extern NSString *DTLinkButtonDidHighlightNotification; 14 | 15 | /** 16 | A button that corresponds to a hyperlink. 17 | 18 | Multiple parts of the same hyperlink synchronize their looks through the guid. 19 | */ 20 | @interface DTLinkButton : UIButton 21 | 22 | 23 | /** 24 | The URL that this button corresponds to. 25 | */ 26 | @property (nonatomic, copy) NSURL *URL; 27 | 28 | 29 | /** 30 | The unique identifier (GUID) that all parts of the same hyperlink have in common. 31 | */ 32 | @property (nonatomic, copy) NSString *GUID; 33 | 34 | 35 | /** 36 | The minimum size that the receiver should respond on hits with. Adjusts the bounds if they are smaller than the passed size. 37 | */ 38 | @property (nonatomic, assign) CGSize minimumHitSize; 39 | 40 | 41 | /** 42 | A Boolean value that determines whether tapping the button causes it to show a gray rounded rectangle. Default is YES. 43 | */ 44 | @property(nonatomic) BOOL showsTouchWhenHighlighted; 45 | 46 | /** 47 | The attributed string to be drawn for the link button. 48 | */ 49 | @property (nonatomic, retain) NSAttributedString *attributedString; 50 | 51 | 52 | /** 53 | The attributed string to be drawn for the link button while it is highlighted. 54 | */ 55 | @property (nonatomic, retain) NSAttributedString *highlightedAttributedString; 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /Core/Source/NSString+Paragraphs.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Paragraphs.m 3 | // DTRichTextEditor 4 | // 5 | // Created by Oliver Drobnik on 11/11/11. 6 | // Copyright (c) 2011 Cocoanetics. All rights reserved. 7 | // 8 | 9 | #import "NSString+Paragraphs.h" 10 | 11 | @implementation NSString (Paragraphs) 12 | 13 | - (NSRange)rangeOfParagraphsContainingRange:(NSRange)range parBegIndex:(NSUInteger *)parBegIndex parEndIndex:(NSUInteger *)parEndIndex 14 | { 15 | // get beginning and end of paragraph containing the replaced range 16 | CFIndex beginIndex; 17 | CFIndex endIndex; 18 | 19 | CFStringGetParagraphBounds((__bridge CFStringRef)self, CFRangeMake(range.location, range.length), &beginIndex, &endIndex, NULL); 20 | 21 | if (parBegIndex) 22 | { 23 | *parBegIndex = beginIndex; 24 | } 25 | 26 | if (parEndIndex) 27 | { 28 | *parEndIndex = endIndex; 29 | } 30 | 31 | return NSMakeRange(beginIndex, endIndex - beginIndex); 32 | } 33 | 34 | - (BOOL)indexIsAtBeginningOfParagraph:(NSUInteger)index 35 | { 36 | // index zero is beginning of first paragraph 37 | if (!index) 38 | { 39 | return YES; 40 | } 41 | 42 | // beginning of any other paragraph is after NL 43 | if ([self characterAtIndex:index-1] == '\n') 44 | { 45 | return YES; 46 | } 47 | 48 | // no beginning 49 | return NO; 50 | } 51 | 52 | - (NSRange)rangeOfParagraphAtIndex:(NSUInteger)index 53 | { 54 | NSUInteger start; 55 | NSUInteger end; 56 | 57 | [self rangeOfParagraphsContainingRange:NSMakeRange(index, 1) parBegIndex:&start parEndIndex:&end]; 58 | 59 | return NSMakeRange(start, end-start); 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /Core/Source/DTCoreTextGlyphRun.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTCoreTextGlyphRun.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/25/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | 10 | 11 | #if TARGET_OS_IPHONE 12 | #import 13 | #elif TARGET_OS_MAC 14 | #import 15 | #endif 16 | 17 | @class DTCoreTextLayoutLine; 18 | @class DTTextAttachment; 19 | 20 | @interface DTCoreTextGlyphRun : NSObject 21 | { 22 | NSRange _stringRange; 23 | } 24 | 25 | - (id)initWithRun:(CTRunRef)run layoutLine:(DTCoreTextLayoutLine *)layoutLine offset:(CGFloat)offset; 26 | 27 | - (CGRect)frameOfGlyphAtIndex:(NSInteger)index; 28 | - (CGRect)imageBoundsInContext:(CGContextRef)context; 29 | - (NSRange)stringRange; 30 | - (NSArray *)stringIndices; 31 | 32 | - (void)drawInContext:(CGContextRef)context; 33 | 34 | - (void)fixMetricsFromAttachment; 35 | 36 | @property (nonatomic, assign, readonly) CGRect frame; 37 | @property (nonatomic, assign, readonly) NSInteger numberOfGlyphs; 38 | @property (nonatomic, unsafe_unretained, readonly) NSDictionary *attributes; // subtle simulator bug - use assign not __unsafe_unretained in 4.2 39 | @property (nonatomic, assign, readonly, getter=isHyperlink) BOOL hyperlink; 40 | 41 | @property (nonatomic, assign, readonly) CGFloat ascent; 42 | @property (nonatomic, assign, readonly) CGFloat descent; 43 | @property (nonatomic, assign, readonly) CGFloat leading; 44 | @property (nonatomic, assign, readonly) CGFloat width; 45 | 46 | @property (nonatomic, strong) DTTextAttachment *attachment; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Core/Source/NSAttributedStringRunDelegates.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedStringRunDelegates.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver on 14.01.11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "NSAttributedStringRunDelegates.h" 10 | #import "DTTextAttachment.h" 11 | 12 | 13 | void embeddedObjectDeallocCallback(void *context) 14 | { 15 | } 16 | 17 | CGFloat embeddedObjectGetAscentCallback(void *context) 18 | { 19 | if ([(__bridge id)context isKindOfClass:[DTTextAttachment class]]) 20 | { 21 | return [(__bridge DTTextAttachment *)context ascentForLayout]; 22 | } 23 | return 0; 24 | } 25 | CGFloat embeddedObjectGetDescentCallback(void *context) 26 | { 27 | if ([(__bridge id)context isKindOfClass:[DTTextAttachment class]]) 28 | { 29 | return [(__bridge DTTextAttachment *)context descentForLayout]; 30 | } 31 | return 0; 32 | } 33 | 34 | CGFloat embeddedObjectGetWidthCallback(void * context) 35 | { 36 | if ([(__bridge id)context isKindOfClass:[DTTextAttachment class]]) 37 | { 38 | return [(__bridge DTTextAttachment *)context displaySize].width; 39 | } 40 | return 35; 41 | } 42 | 43 | CTRunDelegateRef createEmbeddedObjectRunDelegate(id obj) 44 | { 45 | CTRunDelegateCallbacks callbacks; 46 | callbacks.version = kCTRunDelegateCurrentVersion; 47 | callbacks.dealloc = embeddedObjectDeallocCallback; 48 | callbacks.getAscent = embeddedObjectGetAscentCallback; 49 | callbacks.getDescent = embeddedObjectGetDescentCallback; 50 | callbacks.getWidth = embeddedObjectGetWidthCallback; 51 | return CTRunDelegateCreate(&callbacks, (__bridge void *)obj); 52 | return NULL; 53 | } 54 | -------------------------------------------------------------------------------- /Demo/Resources/TextBoxes.html: -------------------------------------------------------------------------------- 1 |

Text Boxes

2 |

Block-Level tags (like P or DIV) can have a padding and background color.

3 |

Note: Only one level of boxing is properly supported. This is a crude workaround as CoreText does not support NSTextBlock on iOS.

4 |
<p style="background-color:yellow;padding:20px;">...</p>
5 |

By default the background is rectangle drawn for the entire width of the content view. There is a delegate method which you can override to do your own drawing. For example a stretchable UIImage. Or a rounded rectangle as shown here.

6 |

7 | 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 in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 8 |

9 |

Some test cases

10 |

Before the list with 10px padding

11 |
    12 |
  • These items
  • have a fixed line height of 50px
  • Note: The line-height becomes the ascender. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
13 |

After the list with 25 px padding

14 | -------------------------------------------------------------------------------- /Core/Source/DTCoreText.h: -------------------------------------------------------------------------------- 1 | #if TARGET_OS_IPHONE 2 | #import 3 | #elif TARGET_OS_MAC 4 | #import 5 | #endif 6 | 7 | // global constants 8 | #import "DTCoreTextConstants.h" 9 | #import "DTCompatibility.h" 10 | 11 | #import "DTColor+HTML.h" 12 | #import "DTImage+HTML.h" 13 | 14 | // common utilities 15 | #import "CGUtils.h" 16 | 17 | // common classes 18 | #import "DTCSSListStyle.h" 19 | #import "DTTextBlock.h" 20 | #import "DTCSSStylesheet.h" 21 | #import "DTCoreTextFontDescriptor.h" 22 | #import "DTHTMLElement.h" 23 | #import "DTTextAttachment.h" 24 | #import "NSCharacterSet+HTML.h" 25 | #import "NSScanner+HTML.h" 26 | #import "NSMutableString+HTML.h" 27 | #import "NSString+CSS.h" 28 | #import "NSString+HTML.h" 29 | #import "NSString+Paragraphs.h" 30 | #import "DTCoreTextParagraphStyle.h" 31 | #import "NSMutableAttributedString+HTML.h" 32 | #import "NSAttributedString+HTML.h" 33 | #import "NSAttributedString+SmallCaps.h" 34 | #import "NSAttributedString+DTCoreText.h" 35 | #import "DTHTMLAttributedStringBuilder.h" 36 | 37 | 38 | // These classes only work with UIKit on iOS 39 | #if TARGET_OS_IPHONE 40 | 41 | #import "DTLazyImageView.h" 42 | #import "DTLinkButton.h" 43 | #import "DTWebVideoView.h" 44 | #import "NSAttributedStringRunDelegates.h" 45 | 46 | #import "DTAttributedTextCell.h" 47 | #import "DTAttributedTextContentView.h" 48 | #import "DTAttributedTextView.h" 49 | #import "DTCoreTextFontCollection.h" 50 | #import "DTCoreTextGlyphRun.h" 51 | #import "DTCoreTextLayoutFrame.h" 52 | #import "DTCoreTextLayoutLine.h" 53 | #import "DTCoreTextLayouter.h" 54 | 55 | #import "UIFont+DTCoreText.h" 56 | 57 | #endif 58 | 59 | 60 | #define DT_ADD_FONT_ON_ATTACHMENTS 61 | -------------------------------------------------------------------------------- /Core/Source/DTHTMLAttributedStringBuilder.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTHTMLAttributedStringBuilder.h 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 21.01.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "DTHTMLParser.h" 10 | 11 | @class DTHTMLElement; 12 | 13 | typedef void(^DTHTMLAttributedStringBuilderWillFlushCallback)(DTHTMLElement *); 14 | 15 | 16 | /** 17 | Class for building an `NSAttributedString` from an HTML document. 18 | */ 19 | @interface DTHTMLAttributedStringBuilder : NSObject 20 | 21 | /** 22 | @name Creating an Attributed String Builder 23 | */ 24 | 25 | /** 26 | Initializes and returns a new `NSAttributedString` object from the HTML contained in the given object and base URL. 27 | @param data The data in HTML format from which to create the attributed string. 28 | @param options Specifies how the document should be loaded. Contains values described in “Option keys for importing documents.” 29 | @param docAttributes Currently not in used. 30 | @returns Returns an initialized object, or `nil` if the data can’t be decoded. 31 | */ 32 | - (id)initWithHTML:(NSData *)data options:(NSDictionary *)options documentAttributes:(NSDictionary **)docAttributes; 33 | 34 | 35 | /** 36 | @name Generating Attributed Strings 37 | */ 38 | 39 | /** 40 | Creates the attributed string when called the first time. 41 | @returns An `NSAttributedString` representing the HTML document passed in the initializer. 42 | */ 43 | - (NSAttributedString *)generatedAttributedString; 44 | 45 | 46 | /** 47 | This block is called before the element is written to the output attributed string 48 | */ 49 | @property (nonatomic, copy) DTHTMLAttributedStringBuilderWillFlushCallback willFlushCallback; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Demo/Resources/LineHeight.html: -------------------------------------------------------------------------------- 1 |

Line Height

2 |

This demonstrates setting of different line heights. Note: Internally this is achieved by setting the minimum and maximum line height in the paragraph style. The paragraph style line height multiplier is ignored.

3 |

Relative Line Heights

4 |

Font Size 25px; Line Height: normal

5 |

Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg

6 |

Font Size 25px; Line Height: 150%

7 |

Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg

8 |

Font Size 25px; Line Height: 2

9 |

Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg

10 | 11 |

Absolut Line Heights

12 | 13 |

Font Size 25px; Line Height: 15px

14 |

Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg

15 |

Font Size 25px; Line Height: 25px

16 |

Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg

17 |

Font Size 25px; Line Height: 40px

18 |

Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg

19 | -------------------------------------------------------------------------------- /Core/Source/NSAttributedString+HTML.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+HTML.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/9/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #if TARGET_OS_IPHONE 10 | #import 11 | #elif TARGET_OS_MAC 12 | #import 13 | #endif 14 | 15 | #import "DTCoreText.h" 16 | 17 | @implementation NSAttributedString (HTML) 18 | 19 | - (id)initWithHTMLData:(NSData *)data documentAttributes:(NSDictionary **)docAttributes 20 | { 21 | return [self initWithHTMLData:data options:nil documentAttributes:docAttributes]; 22 | } 23 | 24 | - (id)initWithHTMLData:(NSData *)data baseURL:(NSURL *)base documentAttributes:(NSDictionary **)docAttributes 25 | { 26 | NSDictionary *optionsDict = nil; 27 | 28 | if (base) 29 | { 30 | optionsDict = [NSDictionary dictionaryWithObject:base forKey:NSBaseURLDocumentOption]; 31 | } 32 | 33 | return [self initWithHTMLData:data options:optionsDict documentAttributes:docAttributes]; 34 | } 35 | 36 | - (id)initWithHTMLData:(NSData *)data options:(NSDictionary *)options documentAttributes:(NSDictionary **)docAttributes 37 | { 38 | // only with valid data 39 | if (![data length]) 40 | { 41 | return nil; 42 | } 43 | 44 | DTHTMLAttributedStringBuilder *stringBuilder = [[DTHTMLAttributedStringBuilder alloc] initWithHTML:data options:options documentAttributes:docAttributes]; 45 | 46 | void (^callBackBlock)(DTHTMLElement *element) = [options objectForKey:DTWillFlushBlockCallBack]; 47 | 48 | if (callBackBlock) 49 | { 50 | [stringBuilder setWillFlushCallback:callBackBlock]; 51 | } 52 | 53 | // This needs to be on a seprate line so that ARC can handle releasing the object properly 54 | // return [stringBuilder generatedAttributedString]; shows leak in instruments 55 | id string = [stringBuilder generatedAttributedString]; 56 | 57 | return string; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Core/Source/DTCSSStylesheet.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTCSSStylesheet.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 9/5/11. 6 | // Copyright (c) 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class DTHTMLElement; 12 | 13 | /** 14 | This class represents a CSS style sheet used for specifying formatting for certain CSS selectors. 15 | 16 | It supports matching styles by class, by id or by tag name. Hierarchy matching is not supported yet. 17 | */ 18 | @interface DTCSSStylesheet : NSObject 19 | 20 | 21 | /** 22 | @name Creating Stylesheets 23 | */ 24 | 25 | /** 26 | Creates the default stylesheet. 27 | 28 | This stylesheet is based on the standard styles that Webkit provides for these tags. This stylesheet is loaded from an embedded copy of default.css. 29 | */ 30 | + (DTCSSStylesheet *)defaultStyleSheet; 31 | 32 | 33 | /** 34 | Creates a stylesheet with a given style block 35 | 36 | @param css The CSS string for the style block 37 | */ 38 | - (id)initWithStyleBlock:(NSString *)css; 39 | 40 | 41 | /** 42 | @name Working with CSS Style Blocks 43 | */ 44 | 45 | 46 | /** 47 | Parses a style block string and adds the found style rules to the receiver. 48 | 49 | @param css The CSS string for the style block 50 | */ 51 | - (void)parseStyleBlock:(NSString *)css; 52 | 53 | 54 | /** 55 | Merges styles from given stylesheet into the receiver 56 | 57 | @param stylesheet the stylesheet to merge 58 | */ 59 | - (void)mergeStylesheet:(DTCSSStylesheet *)stylesheet; 60 | 61 | 62 | /** 63 | @name Accessing Style Information 64 | */ 65 | 66 | /** 67 | Returns a dictionary that contains the merged style for a given element and the applicable style rules from the receiver. 68 | 69 | @param element The HTML element. 70 | @returns The merged style dictionary containing only styles which selector matches the element 71 | */ 72 | - (NSDictionary *)mergedStyleDictionaryForElement:(DTHTMLElement *)element; 73 | 74 | 75 | /** 76 | Returns a dictionary of the styles of the receiver 77 | */ 78 | - (NSDictionary *)styles; 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /Core/Source/DTCoreTextConstants.h: -------------------------------------------------------------------------------- 1 | // unicode characters 2 | 3 | #define UNICODE_OBJECT_PLACEHOLDER @"\ufffc" 4 | #define UNICODE_LINE_FEED @"\u2028" 5 | 6 | // standard options 7 | 8 | #if TARGET_OS_IPHONE 9 | extern NSString * const NSBaseURLDocumentOption; 10 | extern NSString * const NSTextEncodingNameDocumentOption; 11 | extern NSString * const NSTextSizeMultiplierDocumentOption; 12 | extern NSString * const NSAttachmentAttributeName; 13 | #endif 14 | 15 | // custom options 16 | 17 | extern NSString * const DTMaxImageSize; 18 | extern NSString * const DTDefaultFontFamily; 19 | extern NSString * const DTDefaultTextColor; 20 | extern NSString * const DTDefaultLinkColor; 21 | extern NSString * const DTDefaultLinkDecoration; 22 | extern NSString * const DTDefaultTextAlignment; 23 | extern NSString * const DTDefaultLineHeightMultiplier; 24 | extern NSString * const DTDefaultLineHeightMultiplier; 25 | extern NSString * const DTDefaultFirstLineHeadIndent; 26 | extern NSString * const DTDefaultHeadIndent; 27 | extern NSString * const DTDefaultListIndent; 28 | extern NSString * const DTDefaultStyleSheet; 29 | extern NSString * const DTUseiOS6Attributes; 30 | extern NSString * const DTWillFlushBlockCallBack; 31 | 32 | // attributed string attribute constants 33 | 34 | extern NSString * const DTTextListsAttribute; 35 | extern NSString * const DTAttachmentParagraphSpacingAttribute; 36 | extern NSString * const DTLinkAttribute; 37 | extern NSString * const DTAnchorAttribute; 38 | extern NSString * const DTGUIDAttribute; 39 | extern NSString * const DTHeaderLevelAttribute; 40 | extern NSString * const DTPreserveNewlinesAttribute; 41 | extern NSString * const DTStrikeOutAttribute; 42 | extern NSString * const DTBackgroundColorAttribute; 43 | extern NSString * const DTShadowsAttribute; 44 | extern NSString * const DTHorizontalRuleStyleAttribute; 45 | extern NSString * const DTTextBlocksAttribute; 46 | extern NSString * const DTFieldAttribute; 47 | 48 | // iOS 6 compatibility 49 | extern BOOL ___useiOS6Attributes; 50 | 51 | // macros 52 | 53 | #define IS_WHITESPACE(_c) (_c == ' ' || _c == '\t' || _c == 0xA || _c == 0xB || _c == 0xC || _c == 0xD || _c == 0x85) -------------------------------------------------------------------------------- /Core/Source/NSAttributedString+HTML.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+HTML.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/9/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | @class NSAttributedString; 10 | 11 | /** 12 | Methods for generating an `NSAttributedString` from HTML data. Those methods exist on Mac but have not been ported (publicly) to iOS. This project aims to remedy this. 13 | */ 14 | 15 | @interface NSAttributedString (HTML) 16 | 17 | /** 18 | @name Creating an NSAttributedString 19 | */ 20 | 21 | /** 22 | Initializes and returns a new `NSAttributedString` object from the HTML contained in the given object and base URL. 23 | @param data The data in HTML format from which to create the attributed string. 24 | @param docAttributes Currently not in used. 25 | @returns Returns an initialized object, or `nil` if the data can’t be decoded. 26 | */ 27 | - (id)initWithHTMLData:(NSData *)data documentAttributes:(NSDictionary **)docAttributes; 28 | 29 | /** 30 | Initializes and returns a new `NSAttributedString` object from the HTML contained in the given object and base URL. 31 | @param data The data in HTML format from which to create the attributed string. 32 | @param baseURL An `NSURL` that represents the base URL for all links within the HTML. 33 | @param docAttributes Currently not in used. 34 | @returns Returns an initialized object, or `nil` if the data can’t be decoded. 35 | */ 36 | - (id)initWithHTMLData:(NSData *)data baseURL:(NSURL *)baseURL documentAttributes:(NSDictionary **)docAttributes; 37 | 38 | /** 39 | Initializes and returns a new `NSAttributedString` object from the HTML contained in the given object and base URL. 40 | @param data The data in HTML format from which to create the attributed string. 41 | @param options Specifies how the document should be loaded. Contains values described in “Option keys for importing documents.” 42 | @param docAttributes Currently not in used. 43 | @returns Returns an initialized object, or `nil` if the data can’t be decoded. 44 | */ 45 | - (id)initWithHTMLData:(NSData *)data options:(NSDictionary *)options documentAttributes:(NSDictionary **)docAttributes; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Core/Source/DTCoreTextFontDescriptor.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTCoreTextFontDescriptor.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/26/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | @interface DTCoreTextFontDescriptor : NSObject 10 | 11 | // sets the font face name to use for a specific font family 12 | + (void)setSmallCapsFontName:(NSString *)fontName forFontFamily:(NSString *)fontFamily bold:(BOOL)bold italic:(BOOL)italic; 13 | + (NSString *)smallCapsFontNameforFontFamily:(NSString *)fontFamily bold:(BOOL)bold italic:(BOOL)italic; 14 | 15 | // overriding typefaces for families 16 | + (void)setOverrideFontName:(NSString *)fontName forFontFamily:(NSString *)fontFamily bold:(BOOL)bold italic:(BOOL)italic; 17 | + (NSString *)overrideFontNameforFontFamily:(NSString *)fontFamily bold:(BOOL)bold italic:(BOOL)italic; 18 | 19 | + (DTCoreTextFontDescriptor *)fontDescriptorWithFontAttributes:(NSDictionary *)attributes; 20 | + (DTCoreTextFontDescriptor *)fontDescriptorForCTFont:(CTFontRef)ctFont; 21 | 22 | - (id)initWithFontAttributes:(NSDictionary *)attributes; 23 | - (id)initWithCTFontDescriptor:(CTFontDescriptorRef)ctFontDescriptor; 24 | - (id)initWithCTFont:(CTFontRef)ctFont; 25 | 26 | - (void)setFontAttributes:(NSDictionary *)newAttributes; 27 | 28 | - (CTFontSymbolicTraits)symbolicTraits; 29 | - (NSDictionary *)fontAttributes; 30 | 31 | - (CTFontRef)newMatchingFont; 32 | 33 | - (BOOL)supportsNativeSmallCaps; 34 | 35 | - (NSString *)cssStyleRepresentation; 36 | 37 | @property (nonatomic, copy) NSString *fontFamily; 38 | @property (nonatomic, copy) NSString *fontName; 39 | 40 | @property (nonatomic) CGFloat pointSize; 41 | 42 | @property (nonatomic) BOOL boldTrait; 43 | @property (nonatomic) BOOL italicTrait; 44 | @property (nonatomic) BOOL expandedTrait; 45 | @property (nonatomic) BOOL condensedTrait; 46 | @property (nonatomic) BOOL monospaceTrait; 47 | @property (nonatomic) BOOL verticalTrait; 48 | @property (nonatomic) BOOL UIoptimizedTrait; 49 | 50 | @property (nonatomic) CTFontSymbolicTraits symbolicTraits; 51 | 52 | @property (nonatomic) CTFontStylisticClass stylisticClass; 53 | 54 | @property (nonatomic) BOOL smallCapsFeature; 55 | 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /Core/Source/DTCoreTextConstants.m: -------------------------------------------------------------------------------- 1 | #import "DTCoreTextConstants.h" 2 | 3 | // standard options 4 | 5 | #if TARGET_OS_IPHONE 6 | NSString * const NSBaseURLDocumentOption = @"NSBaseURLDocumentOption"; 7 | NSString * const NSTextEncodingNameDocumentOption = @"NSTextEncodingNameDocumentOption"; 8 | NSString * const NSTextSizeMultiplierDocumentOption = @"NSTextSizeMultiplierDocumentOption"; 9 | NSString * const NSAttachmentAttributeName = @"NSAttachmentAttributeName"; 10 | #endif 11 | 12 | // custom options 13 | 14 | NSString * const DTMaxImageSize = @"DTMaxImageSize"; 15 | NSString * const DTDefaultFontFamily = @"DTDefaultFontFamily"; 16 | NSString * const DTDefaultTextColor = @"DTDefaultTextColor"; 17 | NSString * const DTDefaultLinkColor = @"DTDefaultLinkColor"; 18 | NSString * const DTDefaultLinkDecoration = @"DTDefaultLinkDecoration"; 19 | NSString * const DTDefaultTextAlignment = @"DTDefaultTextAlignment"; 20 | NSString * const DTDefaultLineHeightMultiplier = @"DTDefaultLineHeightMultiplier"; 21 | NSString * const DTDefaultFirstLineHeadIndent = @"DTDefaultFirstLineHeadIndent"; 22 | NSString * const DTDefaultHeadIndent = @"DTDefaultHeadIndent"; 23 | NSString * const DTDefaultListIndent = @"DTDefaultListIndent"; 24 | NSString * const DTDefaultStyleSheet = @"DTDefaultStyleSheet"; 25 | NSString * const DTUseiOS6Attributes = @"DTUseiOS6Attributes"; 26 | NSString * const DTWillFlushBlockCallBack = @"DTWillFlushBlockCallBack"; 27 | 28 | // attributed string attribute constants 29 | 30 | NSString * const DTTextListsAttribute = @"DTTextLists"; 31 | NSString * const DTAttachmentParagraphSpacingAttribute = @"DTAttachmentParagraphSpacing"; 32 | NSString * const DTLinkAttribute = @"DTLink"; 33 | NSString * const DTAnchorAttribute = @"DTAnchor"; 34 | NSString * const DTGUIDAttribute = @"DTGUID"; 35 | NSString * const DTHeaderLevelAttribute = @"DTHeaderLevel"; 36 | NSString * const DTPreserveNewlinesAttribute = @"DTPreserveNewlines"; 37 | NSString * const DTStrikeOutAttribute = @"NSStrikethrough"; 38 | NSString * const DTBackgroundColorAttribute = @"DTBackgroundColor"; 39 | NSString * const DTShadowsAttribute = @"DTShadows"; 40 | NSString * const DTHorizontalRuleStyleAttribute = @"DTHorizontalRuleStyle"; 41 | NSString * const DTTextBlocksAttribute = @"DTTextBlocks"; 42 | NSString * const DTFieldAttribute = @"DTField"; 43 | 44 | // iOS 6 compatibility 45 | 46 | BOOL ___useiOS6Attributes = NO; // this gets set globally by DTHTMLAttributedStringBuilder -------------------------------------------------------------------------------- /Core/Source/NSCharacterSet+HTML.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSCharacterSet+HTML.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/15/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | Category on NSCharacterSet to create character sets frequently used and relevant to HTML and CSS string manipulations. Each character set is only initialized once. 13 | */ 14 | @interface NSCharacterSet (HTML) 15 | 16 | 17 | /** 18 | Creates an alpha-numeric character set, appropriate for tag names. 19 | @returns An NSCharacterSet with alpha-numeric characters. a-Z, 0-9. 20 | */ 21 | + (NSCharacterSet *)tagNameCharacterSet; 22 | 23 | 24 | /** 25 | Creates an alpha-numeric character set just as tagNameCharacterSet does but also with colon, dash, and underscore characters, appropriate for tag attribute names. 26 | @returns An NSCharacterSet with alpha-numeric characters and colon :, dash -, and underscore _'. 27 | */ 28 | + (NSCharacterSet *)tagAttributeNameCharacterSet; 29 | 30 | 31 | /** 32 | Creates a character set with the apostrophe character ' (used as single quote agnostic of direction) and double quote character " (agnostic of direction). 33 | @returns An NSCharacterSet with the single quote and double quote characters: ', ". 34 | */ 35 | + (NSCharacterSet *)quoteCharacterSet; 36 | 37 | 38 | /** 39 | Creates a character set with the characters forward slash / and closing angle bracket aka greater than sign >, then forms the union of this character set with the [whitespace character set](https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSCharacterSet_Class/Reference/Reference.html) which includes space, tab, newline, and nextline characters. Useful to find the end of an attribute. 40 | @returns An NSCharacterSet with the forward slash, closing angle bracket characters, tab, space, newline, and nextline characters. 41 | */ 42 | + (NSCharacterSet *)nonQuotedAttributeEndCharacterSet; 43 | 44 | 45 | /** 46 | Creates an alpha-numeric character set just as tagNameCharacterSet does but also with the dash and underscore characters. Does not contain the colon character because it will mess up parsing of CSS style attributes. Useful for CSS attribute names. 47 | @returns An NSCharacterSet with alpha-numeric characters, dash, and underscore characters. 48 | */ 49 | + (NSCharacterSet *)cssStyleAttributeNameCharacterSet; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Core/Source/NSString+CSS.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+CSS.h 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 31.01.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | @class DTColor; 10 | 11 | /** 12 | Methods to make dealing with CSS strings easier. Extract shadows from this string, extract CSS styles found in this string, extract the pixel size of a CSS measurement relative to the current text size, and extract the CSS pixel measurement of this string. 13 | */ 14 | @interface NSString (CSS) 15 | 16 | /** 17 | Examine a string for all CSS styles that are applied to it and return a dictionary of those styles. Implemented using scanCSSAttribute: which is defined in NSScanner+HTML.h. 18 | @returns A dictionary of strings containing the CSS styles which are applied to this string. 19 | */ 20 | - (NSDictionary *)dictionaryOfCSSStyles; 21 | 22 | 23 | /** 24 | Takes a textSize and modifies the current string's pixel measurement to be modified by it. Used in DTHTMLElement. 25 | @param textSize The current size which the CSS size is relative to. 26 | @returns A float that is the size textSize be it %, em or just numbers . 27 | */ 28 | - (CGFloat)pixelSizeOfCSSMeasureRelativeToCurrentTextSize:(CGFloat)textSize; 29 | 30 | 31 | /** 32 | Parse CSS shadow styles, consisting of color, blur, and offset, out of this string. The input string must be comma delimited in the format: ? ? where the third length and the color are not required per CSS shadows. To calculate the sizes of the blur and offset pixelSizeOfCSSMeasureRelativeToCurrentTextSize is used. Used in DTHTMLElement. 33 | @param textSize In order to determine the shadow offset we need what text size it will be displayed at. 34 | @param color Used if no shadow attribute color is found. 35 | @returns An array of dictionaries, each of which is a shadow consisting of color, blur, and offset keys value pairs. 36 | */ 37 | - (NSArray *)arrayOfCSSShadowsWithCurrentTextSize:(CGFloat)textSize currentColor:(DTColor *)color; 38 | 39 | 40 | /** 41 | If this string ends with 'px' return the float value stored therein. Ex: The following '17.0px;' will return 17.0. I DON'T KNOW WHAT USES THIS METHOD IF ANYTHING AT ALL-grep returned just this class 42 | @returns The float value stored in this string. 43 | */ 44 | - (CGFloat)CSSpixelSize; 45 | 46 | /** 47 | Decodes a content attribute which might contained unicode sequences. 48 | */ 49 | - (NSString *)stringByDecodingCSSContentAttribute; 50 | 51 | @end -------------------------------------------------------------------------------- /Core/Source/NSCharacterSet+HTML.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSCharacterSet+HTML.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/15/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "NSCharacterSet+HTML.h" 10 | 11 | static NSCharacterSet *_tagNameCharacterSet = nil; 12 | static NSCharacterSet *_tagAttributeNameCharacterSet = nil; 13 | static NSCharacterSet *_quoteCharacterSet = nil; 14 | static NSCharacterSet *_nonQuotedAttributeEndCharacterSet = nil; 15 | static NSCharacterSet *_cssStyleAttributeNameCharacterSet = nil; 16 | 17 | 18 | @implementation NSCharacterSet (HTML) 19 | 20 | + (NSCharacterSet *)tagNameCharacterSet 21 | { 22 | static dispatch_once_t predicate; 23 | 24 | dispatch_once(&predicate, ^{ 25 | _tagNameCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]; 26 | }); 27 | 28 | return _tagNameCharacterSet; 29 | } 30 | 31 | + (NSCharacterSet *)tagAttributeNameCharacterSet 32 | { 33 | static dispatch_once_t predicate; 34 | 35 | dispatch_once(&predicate, ^{ 36 | _tagAttributeNameCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"-_:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]; 37 | }); 38 | 39 | return _tagAttributeNameCharacterSet; 40 | } 41 | 42 | + (NSCharacterSet *)quoteCharacterSet 43 | { 44 | static dispatch_once_t predicate; 45 | 46 | dispatch_once(&predicate, ^{ 47 | _quoteCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"'\""]; 48 | }); 49 | 50 | return _quoteCharacterSet; 51 | } 52 | 53 | + (NSCharacterSet *)nonQuotedAttributeEndCharacterSet 54 | { 55 | static dispatch_once_t predicate; 56 | 57 | dispatch_once(&predicate, ^{ 58 | NSMutableCharacterSet *tmpCharacterSet = [NSMutableCharacterSet characterSetWithCharactersInString:@"/>"]; 59 | [tmpCharacterSet formUnionWithCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 60 | 61 | _nonQuotedAttributeEndCharacterSet = [tmpCharacterSet copy]; 62 | }); 63 | 64 | return _nonQuotedAttributeEndCharacterSet; 65 | } 66 | 67 | // NOTE: cannot contain : because otherwise this messes up parsing of CSS style attributes 68 | + (NSCharacterSet *)cssStyleAttributeNameCharacterSet 69 | { 70 | static dispatch_once_t predicate; 71 | 72 | dispatch_once(&predicate, ^{ 73 | _cssStyleAttributeNameCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"-_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]; 74 | }); 75 | return _cssStyleAttributeNameCharacterSet; 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /Core/Test/Source/NSString+SlashEscaping.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+SlashEscaping.m 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 02.02.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "NSString+SlashEscaping.h" 10 | 11 | @implementation NSString (SlashEscaping) 12 | 13 | - (NSString *)stringByAddingSlashEscapes 14 | { 15 | NSUInteger length = [self length]; 16 | 17 | unichar *characters = calloc(length, sizeof(unichar)); 18 | unichar *final = calloc(length*2+1, sizeof(unichar)); 19 | 20 | [self getCharacters:characters range:NSMakeRange(0, length)]; 21 | 22 | NSUInteger outChars = 0; 23 | 24 | for (NSUInteger idx=0; idxText Alignment 2 |

Supported are all text alignment options via CSS text-align or the center tag.

3 |

text-align: left, right, center, justify, inherit

4 | 5 |

Examples

6 | 7 |

Right

Center

Left

8 | 9 |

Non-Justified

10 |

This is normal text, naturally aligned

11 |

Proin lectus nisl, volutpat non aliquet id, suscipit sed est. Maecenas vitae metus eget nulla mattis bibendum a a dolor. Fusce vestibulum velit hendrerit nunc egestas non ullamcorper ipsum sagittis. Praesent dui sapien, egestas sed tincidunt vitae, posuere eu neque. Donec faucibus felis sed magna cursus in faucibus odio aliquam. Curabitur elit urna, ullamcorper ut placerat vitae, posuere sed nisl. Maecenas ac tellus nulla, a commodo purus. Suspendisse porta vulputate volutpat. Integer sodales turpis vitae ante sodales tempor. Cras iaculis accumsan ante, sed interdum sem faucibus vel. Aenean tincidunt eleifend elit at porta. Aenean sodales lacus quis lacus gravida consectetur. Sed at ligula ut diam porta auctor. Nunc libero mauris, feugiat id gravida sed, blandit non neque. Suspendisse ac mauris urna. Vivamus lacus ipsum, tincidunt nec laoreet quis, convallis id purus. Ut vehicula lobortis erat in viverra. Ut ut tellus eget ipsum malesuada dignissim. Donec quis dolor erat, id adipiscing ipsum. Donec vitae sagittis turpis. 12 |

13 | 14 |

Justified

15 |

Justified Text is ideal for articles and books because the left and right border appears visually the same

16 | 17 |

Proin lectus nisl, volutpat non aliquet id, suscipit sed est. Maecenas vitae metus eget nulla mattis bibendum a a dolor. Fusce vestibulum velit hendrerit nunc egestas non ullamcorper ipsum sagittis. Praesent dui sapien, egestas sed tincidunt vitae, posuere eu neque. Donec faucibus felis sed magna cursus in faucibus odio aliquam. Curabitur elit urna, ullamcorper ut placerat vitae, posuere sed nisl. Maecenas ac tellus nulla, a commodo purus. Suspendisse porta vulputate volutpat. Integer sodales turpis vitae ante sodales tempor. Cras iaculis accumsan ante, sed interdum sem faucibus vel. Aenean tincidunt eleifend elit at porta. Aenean sodales lacus quis lacus gravida consectetur. Sed at ligula ut diam porta auctor. Nunc libero mauris, feugiat id gravida sed, blandit non neque. Suspendisse ac mauris urna. Vivamus lacus ipsum, tincidunt nec laoreet quis, convallis id purus. Ut vehicula lobortis erat in viverra. Ut ut tellus eget ipsum malesuada dignissim. Donec quis dolor erat, id adipiscing ipsum. Donec vitae sagittis turpis. 18 |

19 |
-------------------------------------------------------------------------------- /Core/Source/NSAttributedString+SmallCaps.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+SmallCaps.m 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 31.01.12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "NSAttributedString+SmallCaps.h" 10 | #import "DTCoreText.h" 11 | 12 | @implementation NSAttributedString (SmallCaps) 13 | 14 | + (NSAttributedString *)synthesizedSmallCapsAttributedStringWithText:(NSString *)text attributes:(NSDictionary *)attributes 15 | { 16 | id normalFont = [attributes objectForKey:(id)kCTFontAttributeName]; 17 | 18 | DTCoreTextFontDescriptor *smallerFontDesc = nil; 19 | 20 | if ([normalFont isKindOfClass:[UIFont class]]) 21 | { 22 | // UIKit Font 23 | UIFont *font = normalFont; 24 | 25 | CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL); 26 | 27 | smallerFontDesc = [DTCoreTextFontDescriptor fontDescriptorForCTFont:ctFont]; 28 | 29 | CFRelease(ctFont); 30 | } 31 | else 32 | { 33 | // CTFontRef 34 | smallerFontDesc = [DTCoreTextFontDescriptor fontDescriptorForCTFont:(__bridge CTFontRef)normalFont]; 35 | } 36 | 37 | smallerFontDesc.pointSize *= 0.7; 38 | CTFontRef smallerFont = [smallerFontDesc newMatchingFont]; 39 | 40 | NSMutableDictionary *smallAttributes = [attributes mutableCopy]; 41 | 42 | if (___useiOS6Attributes) 43 | { 44 | UIFont *uiFont = [UIFont fontWithCTFont:smallerFont]; 45 | 46 | [smallAttributes setObject:uiFont forKey:NSFontAttributeName]; 47 | 48 | CFRelease(smallerFont); 49 | } 50 | else 51 | { 52 | [smallAttributes setObject:CFBridgingRelease(smallerFont) forKey:(id)kCTFontAttributeName]; 53 | } 54 | 55 | NSMutableAttributedString *tmpString = [[NSMutableAttributedString alloc] init]; 56 | NSScanner *scanner = [NSScanner scannerWithString:text]; 57 | [scanner setCharactersToBeSkipped:nil]; 58 | 59 | NSCharacterSet *lowerCaseChars = [NSCharacterSet lowercaseLetterCharacterSet]; 60 | 61 | while (![scanner isAtEnd]) 62 | { 63 | NSString *part; 64 | 65 | if ([scanner scanCharactersFromSet:lowerCaseChars intoString:&part]) 66 | { 67 | part = [part uppercaseString]; 68 | NSAttributedString *partString = [[NSAttributedString alloc] initWithString:part attributes:smallAttributes]; 69 | [tmpString appendAttributedString:partString]; 70 | } 71 | 72 | if ([scanner scanUpToCharactersFromSet:lowerCaseChars intoString:&part]) 73 | { 74 | NSAttributedString *partString = [[NSAttributedString alloc] initWithString:part attributes:attributes]; 75 | [tmpString appendAttributedString:partString]; 76 | } 77 | } 78 | 79 | return tmpString; 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /Core/Source/DTCoreTextLayoutLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTCoreTextLayoutLine.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/24/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | 10 | #if TARGET_OS_IPHONE 11 | #import 12 | #elif TARGET_OS_MAC 13 | #import 14 | #endif 15 | 16 | @class DTCoreTextLayoutFrame; 17 | 18 | @interface DTCoreTextLayoutLine : NSObject 19 | { 20 | NSInteger _stringLocationOffset; // offset to modify internal string location to get actual location 21 | } 22 | 23 | - (id)initWithLine:(CTLineRef)line; 24 | 25 | - (NSRange)stringRange; 26 | - (NSInteger)numberOfGlyphs; 27 | - (CGRect)frameOfGlyphAtIndex:(NSInteger)index; 28 | - (NSArray *)glyphRunsWithRange:(NSRange)range; 29 | - (CGRect)frameOfGlyphsWithRange:(NSRange)range; 30 | - (CGRect)imageBoundsInContext:(CGContextRef)context; 31 | - (NSArray *)stringIndices; 32 | - (CGFloat)offsetForStringIndex:(NSInteger)index; 33 | - (NSInteger)stringIndexForPosition:(CGPoint)position; 34 | 35 | 36 | /** 37 | @name Creating Variants 38 | */ 39 | 40 | /** 41 | Creates a version of the receiver that is justified to the given width. 42 | 43 | @param justificationFactor Full or partial justification. When set to `1.0` or greater, full justification is performed. If this parameter is set to less than `1.0`, varying degrees of partial justification are performed. If it is set to `0` or less, no justification is performed. 44 | @param justificationWidth The width to which the resultant line is justified. If justificationWidth is less than the actual width of the line, then negative justification is performed (that is, glyphs are squeezed together). 45 | */ 46 | - (DTCoreTextLayoutLine *)justifiedLineWithFactor:(CGFloat)justificationFactor justificationWidth:(CGFloat)justificationWidth; 47 | 48 | 49 | 50 | - (void)drawInContext:(CGContextRef)context; 51 | 52 | 53 | /** Adjust the baselines of all lines in this layout frame to fit the heights of text attachments. 54 | 55 | This is used to work around a CoreText bug that was fixed in iOS 4.2 56 | 57 | @returns `YES` if the line needed an adjustment, `NO` if no adjustment was carried out 58 | */ 59 | - (BOOL)correctAttachmentHeights:(CGFloat *)downShift; 60 | 61 | 62 | @property (nonatomic, assign) CGRect frame; 63 | @property (nonatomic, strong, readonly) NSArray *glyphRuns; 64 | 65 | @property (nonatomic, assign) CGFloat ascent; // needs to be modifyable 66 | @property (nonatomic, assign, readonly) CGFloat descent; 67 | @property (nonatomic, assign, readonly) CGFloat leading; 68 | @property (nonatomic, assign, readonly) CGFloat trailingWhitespaceWidth; 69 | 70 | @property (nonatomic, assign) CGPoint baselineOrigin; 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /Core/Source/NSMutableAttributedString+HTML.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableAttributedString+HTML.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 4/14/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "NSMutableAttributedString+HTML.h" 10 | 11 | #import "DTCoreText.h" 12 | 13 | @implementation NSMutableAttributedString (HTML) 14 | 15 | 16 | // appends a plain string extending the attributes at this position 17 | - (void)appendString:(NSString *)string 18 | { 19 | NSUInteger selfLengthBefore = [self length]; 20 | 21 | [self.mutableString appendString:string]; 22 | 23 | NSRange appendedStringRange = NSMakeRange(selfLengthBefore, [string length]); 24 | 25 | // we need to remove the image placeholder (if any) to prevent duplication 26 | [self removeAttribute:NSAttachmentAttributeName range:appendedStringRange]; 27 | [self removeAttribute:(id)kCTRunDelegateAttributeName range:appendedStringRange]; 28 | } 29 | 30 | - (void)appendString:(NSString *)string withParagraphStyle:(DTCoreTextParagraphStyle *)paragraphStyle fontDescriptor:(DTCoreTextFontDescriptor *)fontDescriptor 31 | { 32 | NSUInteger selfLengthBefore = [self length]; 33 | 34 | [self.mutableString appendString:string]; 35 | 36 | NSRange appendedStringRange = NSMakeRange(selfLengthBefore, [string length]); 37 | 38 | if (paragraphStyle || fontDescriptor) 39 | { 40 | NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; 41 | 42 | if (paragraphStyle) 43 | { 44 | if (___useiOS6Attributes) 45 | { 46 | NSParagraphStyle *style = [paragraphStyle NSParagraphStyle]; 47 | [attributes setObject:style forKey:NSParagraphStyleAttributeName]; 48 | } 49 | else 50 | { 51 | CTParagraphStyleRef newParagraphStyle = [paragraphStyle createCTParagraphStyle]; 52 | [attributes setObject:CFBridgingRelease(newParagraphStyle) forKey:(id)kCTParagraphStyleAttributeName]; 53 | } 54 | } 55 | 56 | if (fontDescriptor) 57 | { 58 | CTFontRef newFont = [fontDescriptor newMatchingFont]; 59 | 60 | if (___useiOS6Attributes) 61 | { 62 | // convert to UIFont 63 | UIFont *uiFont = [UIFont fontWithCTFont:newFont]; 64 | [attributes setObject:uiFont forKey:NSFontAttributeName]; 65 | 66 | CFRelease(newFont); 67 | } 68 | else 69 | { 70 | [attributes setObject:CFBridgingRelease(newFont) forKey:(id)kCTFontAttributeName]; 71 | } 72 | } 73 | 74 | // Replace attributes 75 | [self setAttributes:attributes range:appendedStringRange]; 76 | } 77 | else 78 | { 79 | // Remove attributes 80 | [self setAttributes:[NSDictionary dictionary] range:appendedStringRange]; 81 | } 82 | } 83 | 84 | // appends a string without any attributes 85 | - (void)appendNakedString:(NSString *)string 86 | { 87 | [self appendString:string withParagraphStyle:nil fontDescriptor:nil]; 88 | } 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /Demo/Resources/README.html: -------------------------------------------------------------------------------- 1 |

NSAttributedString HTML Additions

2 |

Introduction

3 |

This project aims to duplicate the methods present on Mac OSX which allow creation of NSAttributedString from HTML code. 4 | This is useful for drawing simple rich text - like this document - without having to use a UIWebView.

5 |

6 |

Hi! I'm Oliver and I appreciate your help!

7 |

Features

8 |

At present the following tags are supported:

9 |
  • Headers H1 - H6
  • Paragraphs: P
  • Bold: B, STRONG
  • Italic: I, EM
  • 10 |
  • Underline U
  • 11 |
  • Superscript SUP and Subscript SUB, e.g. e = mc2
  • 12 |
  • Strike Out DEL, e.g. Something Removed or STRIKE e.g. Something Striked
  • 13 |
  • FONT (face,color and size 1-7)
  • 14 |
  • Horizontal Rule HR (coloring via background-color style, simplified)
  • 15 |
  • Hyperlinks A (Formatting only, clickable as custom view)
  • 16 |
  • Unordered Lists LI
  • 17 |
  • Ordered Lists OL
18 |

Currently attributes are inherited from enclosed tags via brute force. I don't know if this is accurate.

19 |

Known Differences

20 |

In many aspects DTCoreText is superior to the Mac version of generating NSAttributedStrings from HTML. These become apparent in the MacUnitTest where the output from both is directly compared. I am summarizing them here for references.

21 |

In the following "Mac" means the initWithHTML: methods there, "DTCoreText" means DTCoreText's initWithHTML and/or DTHTMLAttributedStringBuilder

22 |
    23 |
  • Mac does not support the video tag, DTCoreText does.
  • 24 |
  • DTCoreText is able to synthesize small caps by putting all characters in upper case and using a second smaller font for lowercase characters.
  • 25 |
  • I suspect that Mac makes use of the -webkit-margin-* CSS styles for spacing the paragraphs, DTCoreText only uses the -webkit-margin-bottom and margin-bottom at present.
  • 26 |
  • Mac supports CSS following addresses, e.g. "ul ul" to change the list style for stacked lists. DTCoreText does not support that and so list bullets stay the same for multiple levels.
  • 27 |
28 | 29 |

Please Help!

30 |

If you find brief test cases where the created NSAttributedString differs from the version on OSX please send them to us!

31 |

Also there are many small things that you could help this project with. You can either implement these yourself or sponsor their development.

32 |

Follow @cocoanetics on Twitter

33 |

This code is covered by a BSD License. © 2011 Oliver Drobnik

-------------------------------------------------------------------------------- /Core/Source/NSString+HTML.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+HTML.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/9/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | /** 10 | Methods for making HTML strings easier and quicker to handle. 11 | */ 12 | @interface NSString (HTML) 13 | 14 | /** 15 | Extract the numbers from this string and return them as an NSUInteger. 16 | @returns An NSUInteger of the number characters in this string. 17 | */ 18 | - (NSUInteger)integerValueFromHex; 19 | 20 | 21 | /** 22 | Test whether or not this string is numeric only. 23 | @returns If this string consists only of numeric characters 0-9. 24 | */ 25 | - (BOOL)isNumeric; 26 | 27 | 28 | /** 29 | Read through this string and store the numbers included, then divide them by 100 giving a percentage. 30 | @returns The numbers contained in this string, as a percentage. 31 | */ 32 | - (float)percentValue; 33 | 34 | 35 | /** 36 | Return a copy of this string with all whitespace characters replaced by space characters. 37 | @returns A copy of this string with only space characters for whitespace. 38 | */ 39 | - (NSString *)stringByNormalizingWhitespace; 40 | 41 | 42 | /** 43 | Determines if the first character of this string is in the parameter characterSet. 44 | @param characterSet The character set to compare the first character of this string against. 45 | @returns If the first character of this string is in character set. 46 | */ 47 | - (BOOL)hasPrefixCharacterFromSet:(NSCharacterSet *)characterSet; 48 | 49 | 50 | /** 51 | Determines if the last character of this string is in the parameter characterSet. 52 | @param characterSet The character set to compare the last character of this string against. 53 | @returns If the last character of this string is in the character set. 54 | */ 55 | - (BOOL)hasSuffixCharacterFromSet:(NSCharacterSet *)characterSet; 56 | 57 | 58 | /** 59 | Convert a string into a proper HTML string by converting special characters into HTML entities. For example: an ellipsis `…` is represented by the entity `…` in order to display it correctly across text encodings. 60 | @returns A string containing HTML that now uses proper HTML entities. 61 | */ 62 | - (NSString *)stringByAddingHTMLEntities; 63 | 64 | 65 | /** 66 | Convert a string from HTML entities into correct character representations using UTF8 encoding. For example: an ellipsis entity representy by `…` is converted into `…`. 67 | @returns A string without HTML entities, instead having the actual characters formerly represented by HTML entities. 68 | */ 69 | - (NSString *)stringByReplacingHTMLEntities; 70 | 71 | 72 | /** 73 | Create a globally unique identifier to uniquely identify something. Used to create a GUID, store it in a dictionary or other data structure and retrieve it to uniquely identifiy something. In DTLinkButton multiple parts of the same hyperlink synchronize their looks through the GUID. 74 | @returns GUID assigned to this string to easily and uniquely identify it.. 75 | */ 76 | + (NSString *)guid; 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /Core/Source/DTHTMLElement.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTHTMLElement.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 4/14/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | @class DTCoreTextParagraphStyle; 10 | @class DTCoreTextFontDescriptor; 11 | @class DTTextAttachment; 12 | @class DTCSSListStyle; 13 | @class DTColor; 14 | 15 | typedef enum 16 | { 17 | DTHTMLElementDisplayStyleInline = 0, // default 18 | DTHTMLElementDisplayStyleNone, 19 | DTHTMLElementDisplayStyleBlock, 20 | DTHTMLElementDisplayStyleListItem, 21 | DTHTMLElementDisplayStyleTable, 22 | } DTHTMLElementDisplayStyle; 23 | 24 | typedef enum 25 | { 26 | DTHTMLElementFloatStyleNone = 0, 27 | DTHTMLElementFloatStyleLeft, 28 | DTHTMLElementFloatStyleRight 29 | } DTHTMLElementFloatStyle; 30 | 31 | typedef enum 32 | { 33 | DTHTMLElementFontVariantInherit = 0, 34 | DTHTMLElementFontVariantNormal, 35 | DTHTMLElementFontVariantSmallCaps 36 | } DTHTMLElementFontVariant; 37 | 38 | @interface DTHTMLElement : NSObject 39 | 40 | @property (nonatomic, strong) DTHTMLElement *parent; 41 | @property (nonatomic, copy) DTCoreTextFontDescriptor *fontDescriptor; 42 | @property (nonatomic, copy) DTCoreTextParagraphStyle *paragraphStyle; 43 | @property (nonatomic, strong) DTTextAttachment *textAttachment; 44 | @property (nonatomic, copy) NSURL *link; 45 | @property (nonatomic, copy) NSString *anchorName; 46 | @property (nonatomic, strong) DTColor *textColor; 47 | @property (nonatomic, strong) DTColor *backgroundColor; 48 | @property (nonatomic, copy) NSString *tagName; 49 | @property (nonatomic, copy) NSString *beforeContent; 50 | @property (nonatomic, copy) NSString *text; 51 | @property (nonatomic, copy) NSArray *shadows; 52 | @property (nonatomic, assign) CTUnderlineStyle underlineStyle; 53 | @property (nonatomic, assign) BOOL tagContentInvisible; 54 | @property (nonatomic, assign) BOOL strikeOut; 55 | @property (nonatomic, assign) NSInteger superscriptStyle; 56 | @property (nonatomic, assign) NSInteger headerLevel; 57 | @property (nonatomic, assign) DTHTMLElementDisplayStyle displayStyle; 58 | @property (nonatomic, readonly) DTHTMLElementFloatStyle floatStyle; 59 | @property (nonatomic, assign) BOOL isColorInherited; 60 | @property (nonatomic, assign) BOOL preserveNewlines; 61 | @property (nonatomic, assign) DTHTMLElementFontVariant fontVariant; 62 | @property (nonatomic, assign) CGFloat textScale; 63 | @property (nonatomic, assign) CGSize size; 64 | @property (nonatomic, strong) NSDictionary *attributes; 65 | 66 | - (NSAttributedString *)attributedString; 67 | - (NSDictionary *)attributesDictionary; 68 | 69 | - (void)parseStyleString:(NSString *)styleString; 70 | - (void)applyStyleDictionary:(NSDictionary *)styles; 71 | - (NSDictionary *)styles; 72 | 73 | - (void)addAdditionalAttribute:(id)attribute forKey:(id)key; 74 | 75 | - (NSString *)path; 76 | 77 | - (NSString *)attributeForKey:(NSString *)key; 78 | 79 | - (void)addChild:(DTHTMLElement *)child; 80 | - (void)removeChild:(DTHTMLElement *)child; 81 | 82 | - (DTHTMLElement *)parentWithTagName:(NSString *)name; 83 | - (BOOL)isContainedInBlockElement; 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /Demo/Resources/Image.html: -------------------------------------------------------------------------------- 1 |

Image Handling

2 |

Some basic image handling has been implemented

3 |

Inline

4 |

So far images work inline, sitting on the line's baseline. There is a workaround in place that if an image is more than twice as high as the surrounding text it will be treated as it's own block.

5 |

Block

6 |

There is a known issue with images as blocks, outside of p tags.

7 | 8 |

An Image outside of P is treated as a paragraph

9 |

The previous image has float style. As a Workaround a newline is added after it until we can support floating in the layouting. This is done if the inline image is more than 5 times as large as the current font pixel size. This should allow small inline images, like smileys to stay in line, while most float images would probably be larger than this.

10 |

Supported Attributes

11 |
    12 |
  • width, height in pixels
  • 13 |
  • src with a local file path relative to the app bundle
  • 14 |
15 |

Supported Image Formats

16 |

According to Apple the following image formats are supported for use with UIImage:

17 |
    18 |
  • png
  • 19 |
  • tif, tiff
  • 20 |
  • jpeg, jpg
  • 21 |
  • gif
  • 22 |
  • bmp, bmpf
  • 23 |
  • ico
  • 24 |
  • cur
  • 25 |
  • xbm
  • 26 |
27 |

Vertical Alignment

28 |

Limited support for the CSS vertical-align tag exists

29 |

Baseline:

30 |

text-top:

31 |

text-bottom:

32 |

middle:

33 |

Data Source

34 |

Base64 encoded data SRC is supported, for example this red dot: Red dot

37 |

Another example:

Base64 encoded image 41 | 42 |

Remote Images

43 |

Images can also be loaded from remote URLs, even without specifying a size in the HTML. The code demonstrates how to update the DTTextAttachment's display size after download and triggering a re-layout.

44 | 45 | -------------------------------------------------------------------------------- /Core/Source/DTWebVideoView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTWebVideoView.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 8/5/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "DTWebVideoView.h" 10 | #import "DTTextAttachment.h" 11 | 12 | 13 | @interface DTWebVideoView () 14 | 15 | - (void)disableScrolling; 16 | 17 | @end 18 | 19 | 20 | @implementation DTWebVideoView 21 | { 22 | DTTextAttachment *_attachment; 23 | 24 | __unsafe_unretained id _delegate; 25 | 26 | UIWebView *_webView; 27 | } 28 | 29 | - (id)initWithFrame:(CGRect)frame 30 | { 31 | self = [super initWithFrame:frame]; 32 | if (self) 33 | { 34 | self.userInteractionEnabled = YES; 35 | 36 | _webView = [[UIWebView alloc] initWithFrame:self.bounds]; 37 | _webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 38 | [self addSubview:_webView]; 39 | 40 | [self disableScrolling]; 41 | 42 | _webView.delegate = self; 43 | 44 | if ([_webView respondsToSelector:@selector(setAllowsInlineMediaPlayback:)]) 45 | { 46 | _webView.allowsInlineMediaPlayback = YES; 47 | } 48 | } 49 | return self; 50 | } 51 | 52 | - (void)dealloc 53 | { 54 | _webView.delegate = nil; 55 | 56 | } 57 | 58 | 59 | - (void)disableScrolling 60 | { 61 | // find scrollview and disable scrolling 62 | for (UIView *oneView in _webView.subviews) 63 | { 64 | if ([oneView isKindOfClass:[UIScrollView class]]) 65 | { 66 | UIScrollView *scrollView = (id)oneView; 67 | 68 | scrollView.scrollEnabled = NO; 69 | scrollView.bounces = NO; 70 | 71 | return; 72 | } 73 | } 74 | } 75 | 76 | #pragma mark UIWebViewDelegate 77 | 78 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 79 | { 80 | // only allow the embed request 81 | if ([[[request URL] absoluteString] hasPrefix:@"http://www.youtube.com/embed/"]) 82 | { 83 | return YES; 84 | } 85 | 86 | BOOL shouldOpenExternalURL = YES; 87 | 88 | if ([_delegate respondsToSelector:@selector(videoView:shouldOpenExternalURL:)]) 89 | { 90 | shouldOpenExternalURL = [_delegate videoView:self shouldOpenExternalURL:[request URL]]; 91 | } 92 | 93 | if (shouldOpenExternalURL) 94 | { 95 | [[UIApplication sharedApplication] openURL:[request URL]]; 96 | } 97 | 98 | return NO; 99 | } 100 | 101 | 102 | 103 | #pragma mark Properties 104 | 105 | - (void)setAttachment:(DTTextAttachment *)attachment 106 | { 107 | if (_attachment != attachment) 108 | { 109 | 110 | _attachment = attachment; 111 | } 112 | 113 | switch (attachment.contentType) 114 | { 115 | case DTTextAttachmentTypeIframe: 116 | { 117 | NSURLRequest *request = [NSURLRequest requestWithURL:attachment.contentURL]; 118 | [_webView loadRequest:request]; 119 | break; 120 | } 121 | 122 | default: 123 | break; 124 | } 125 | } 126 | 127 | @synthesize delegate = _delegate; 128 | @synthesize attachment = _attachment; 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /Demo/Resources/styles.html: -------------------------------------------------------------------------------- 1 |

CSS Styles Support

2 |

Currently CSS styling is supported inline in tags, and via simple rules.

3 |

For example:

4 |

<p style="color:red; font-size: 30px;text-decoration:underline;">Some fancy text</p>

5 |

Some fancy text

6 |

This way you can affect the way hyperlinks are decorated: @cocoanetics versus @cocoanetics

7 | 8 |

CSS Rules

9 | 10 |

One or more style blocks are merged into a style sheet. Multiple matching styles are combined in this order, later styles overriding earlier ones.

11 |
    12 |
  • Tag Name
  • 13 |
  • Class Name (multiple classes possible)
  • 14 |
  • Class Name + Tag Name
  • 15 |
  • Tag ID
  • 16 |
  • Local Style attribute of Tag
  • 17 |
18 |

Not yet implemented are pseudo-selectors (first child).

19 | 20 |

Supported Attributes

21 |
    22 |
  • color - names (red) or hex (#FF0000) or rbg rgb(255, 0, 0);
  • 23 |
  • decoration - underline, line-through
  • 24 |
  • font-size - specified in pixels (12px)
  • 25 |
  • font-family - to specify the postscript name of a family like Courier
  • 26 |
  • font-style - normal, inherit, italic, oblique
  • 27 |
  • font-weight - normal, bold
  • 28 |
29 | 30 |

Font Families

31 |
32 |

serif

33 |

sans-serif

34 |

monospace

35 |

cursive

36 |

fantasy

37 |
38 |

Text Shadows

39 |

The text-shadow property is supported.

40 |

Text with Shadow

41 |

Embossed

42 |

Glowing

43 |

Multiple Shadows

46 |

Text with Shadow

47 |

OUTLINE

48 | 49 |

To Do

50 |
    51 |
  • More robust mapping of font family to available fonts, support for fallback
  • 52 |
  • text-decoration - blink, overline
  • 53 |
  • font-variant - to support smallcaps
54 | -------------------------------------------------------------------------------- /Core/Source/DTTextAttachment.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTTextAttachment.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver on 14.01.11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #if TARGET_OS_IPHONE 10 | #import 11 | #elif TARGET_OS_MAC 12 | #import 13 | #endif 14 | 15 | @class DTHTMLElement; 16 | 17 | typedef enum 18 | { 19 | DTTextAttachmentTypeImage, 20 | DTTextAttachmentTypeVideoURL, 21 | DTTextAttachmentTypeIframe, 22 | DTTextAttachmentTypeObject, 23 | DTTextAttachmentTypeGeneric 24 | } DTTextAttachmentType; 25 | 26 | typedef enum 27 | { 28 | DTTextAttachmentVerticalAlignmentBaseline = 0, 29 | DTTextAttachmentVerticalAlignmentTop, 30 | DTTextAttachmentVerticalAlignmentCenter, 31 | DTTextAttachmentVerticalAlignmentBottom 32 | } DTTextAttachmentVerticalAlignment; 33 | 34 | /** 35 | An object to represent an attachment in an HTML/rich text view. 36 | */ 37 | @interface DTTextAttachment : NSObject 38 | 39 | @property (nonatomic, assign) CGSize originalSize; 40 | @property (nonatomic, assign) CGSize displaySize; 41 | @property (nonatomic, strong) id contents; 42 | @property (nonatomic, assign) DTTextAttachmentType contentType; 43 | @property (nonatomic, strong) NSURL *contentURL; 44 | @property (nonatomic, strong) NSURL *hyperLinkURL; 45 | @property (nonatomic, strong) NSString *hyperLinkGUID; // identifies the hyperlink this is part of 46 | @property (nonatomic, strong) NSDictionary *attributes; 47 | @property (nonatomic, assign) DTTextAttachmentVerticalAlignment verticalAlignment; 48 | 49 | /** 50 | Initialize and return a DTTextAttachment with the specified DTHTMLElement and options. Convenience initializer. 51 | The element must have a valid tagName. The size of the returned text attachment is determined by the element, constrained by the option's key for DTMaxImageSize. Any valid image resource included in the element (denoted by the method attributeForKey: "src") is loaded and determines the text attachment size if it was not known before. If a size is too large the image is downsampled with sizeThatFitsKeepingAspectRatio() which preserves the aspect ratio. 52 | @param element A DTHTMLElement that must have a valid tag name and should have a size. Any element attributes are copied to the text attachment's elements. 53 | @param options An NSDictionary of options. Used to specify the max image size with the key DTMaxImageSize. 54 | @returns Returns an initialized DTTextAttachment built using the element and options parameters. 55 | */ 56 | + (DTTextAttachment *)textAttachmentWithElement:(DTHTMLElement *)element options:(NSDictionary *)options; 57 | 58 | 59 | /** 60 | Retrieves a string which is in the format "data:image/png;base64,%@" with this DTTextAttachment's content's data representation encoded in Base64 string encoding. For image contents only. 61 | @returns A Base64 encoded string of the png data representation of this text attachment's image contents. 62 | */ 63 | - (NSString *)dataURLRepresentation; 64 | 65 | 66 | - (void)adjustVerticalAlignmentForFont:(CTFontRef)font; 67 | 68 | 69 | // Customized ascend and descent for the run delegates 70 | - (CGFloat)ascentForLayout; 71 | - (CGFloat)descentForLayout; 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /Core/Source/CGUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // CGUtils.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/16/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "CGUtils.h" 10 | 11 | 12 | // deprecated: use bezierPathWithRoundedRect instead 13 | 14 | //CGPathRef newPathForRoundedRect(CGRect rect, CGFloat cornerRadius, BOOL roundTopCorners, BOOL roundBottomCorners) 15 | // 16 | //{ 17 | // CGMutablePathRef retPath = CGPathCreateMutable(); 18 | // 19 | // CGRect innerRect = CGRectInset(rect, cornerRadius, cornerRadius); 20 | // 21 | // CGFloat inside_right = innerRect.origin.x + innerRect.size.width; 22 | // CGFloat outside_right = rect.origin.x + rect.size.width; 23 | // CGFloat inside_bottom = innerRect.origin.y + innerRect.size.height; 24 | // CGFloat outside_bottom = rect.origin.y + rect.size.height; 25 | // 26 | // CGFloat inside_top = innerRect.origin.y; 27 | // CGFloat outside_top = rect.origin.y; 28 | // CGFloat outside_left = rect.origin.x; 29 | // 30 | // 31 | // if (roundTopCorners) 32 | // { 33 | // CGPathMoveToPoint(retPath, NULL, innerRect.origin.x, outside_top); 34 | // CGPathAddLineToPoint(retPath, NULL, inside_right, outside_top); 35 | // 36 | // CGPathAddArcToPoint(retPath, NULL, outside_right, outside_top, outside_right, inside_top, cornerRadius); 37 | // } 38 | // else 39 | // { 40 | // CGPathMoveToPoint(retPath, NULL, outside_left, outside_top); 41 | // CGPathAddLineToPoint(retPath, NULL, outside_right, outside_top); 42 | // 43 | // } 44 | // 45 | // if (roundBottomCorners) 46 | // { 47 | // CGPathAddLineToPoint(retPath, NULL, outside_right, inside_bottom); 48 | // CGPathAddArcToPoint(retPath, NULL, outside_right, outside_bottom, inside_right, outside_bottom, cornerRadius); 49 | // 50 | // CGPathAddLineToPoint(retPath, NULL, innerRect.origin.x, outside_bottom); 51 | // CGPathAddArcToPoint(retPath, NULL, outside_left, outside_bottom, outside_left, inside_bottom, cornerRadius); 52 | // } 53 | // else 54 | // { 55 | // CGPathAddLineToPoint(retPath, NULL, outside_right, outside_bottom); 56 | // CGPathAddLineToPoint(retPath, NULL, outside_left, outside_bottom); 57 | // } 58 | // 59 | // 60 | // 61 | // if (roundTopCorners) 62 | // { 63 | // CGPathAddLineToPoint(retPath, NULL, outside_left, inside_top); 64 | // CGPathAddArcToPoint(retPath, NULL, outside_left, outside_top, innerRect.origin.x, outside_top, cornerRadius); 65 | // } 66 | // else 67 | // { 68 | // CGPathAddLineToPoint(retPath, NULL, rect.origin.x, outside_top); 69 | // 70 | // } 71 | // 72 | // 73 | // CGPathCloseSubpath(retPath); 74 | // 75 | // return retPath; 76 | //} 77 | 78 | CGSize sizeThatFitsKeepingAspectRatio2(CGSize originalSize, CGSize sizeToFit) 79 | { 80 | if (originalSize.width <= sizeToFit.width && originalSize.height <= sizeToFit.height) 81 | { 82 | return originalSize; 83 | } 84 | 85 | CGFloat necessaryZoomWidth = sizeToFit.width / originalSize.width; 86 | CGFloat necessaryZoomHeight = sizeToFit.height / originalSize.height; 87 | 88 | CGFloat smallerZoom = MIN(necessaryZoomWidth, necessaryZoomHeight); 89 | 90 | CGSize scaledSize = CGSizeMake(roundf(originalSize.width*smallerZoom), roundf(originalSize.height*smallerZoom)); 91 | 92 | return scaledSize; 93 | } 94 | 95 | 96 | CGPoint CGRectCenter(CGRect rect) 97 | { 98 | return (CGPoint){ CGRectGetMidX(rect), CGRectGetMidY(rect) }; 99 | } 100 | -------------------------------------------------------------------------------- /Core/Source/NSAttributedString+DTCoreText.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+DTCoreText.h 3 | // DTCoreText 4 | // 5 | // Created by Oliver Drobnik on 2/1/12. 6 | // Copyright (c) 2012 Drobnik.com. All rights reserved. 7 | // 8 | 9 | @class DTCSSListStyle; 10 | @class DTTextBlock; 11 | 12 | /** 13 | Convenience Methods that mimick similar methods available on Mac 14 | */ 15 | @interface NSAttributedString (DTCoreText) 16 | 17 | /** 18 | @name Working with Text Attachments 19 | */ 20 | 21 | 22 | /** 23 | Retrieves the DTTextAttachment objects that match the given predicate. 24 | 25 | With this method you can for example find all images that have a certain URL. 26 | 27 | @param predicate The predicate to apply for filtering or `nil` for all attachments. 28 | @returns The filtered array of attachments 29 | */ 30 | - (NSArray *)textAttachmentsWithPredicate:(NSPredicate *)predicate; 31 | 32 | 33 | /** 34 | @name Calculating Ranges 35 | */ 36 | 37 | 38 | /** 39 | Returns the index of the item at the given location within the list. 40 | 41 | @param list The text list. 42 | @param location The location of the item. 43 | @returns Returns the index within the list. 44 | */ 45 | - (NSInteger)itemNumberInTextList:(DTCSSListStyle *)list atIndex:(NSUInteger)location; 46 | 47 | 48 | /** 49 | Returns the range of the given text list that contains the given location. 50 | 51 | @param list The text list. 52 | @param location The location in the text. 53 | @returns The range of the given text list containing the location. 54 | */ 55 | - (NSRange)rangeOfTextList:(DTCSSListStyle *)list atIndex:(NSUInteger)location; 56 | 57 | 58 | /** 59 | Returns the range of the given text block that contains the given location. 60 | 61 | @param textBlock The text block. 62 | @param location The location in the text. 63 | @returns The range of the given text block containing the location. 64 | */ 65 | - (NSRange)rangeOfTextBlock:(DTTextBlock *)textBlock atIndex:(NSUInteger)location; 66 | 67 | /** 68 | Returns the range of the given href anchor. 69 | 70 | @param anchorName The name of the anchor. 71 | @returns The range of the given anchor. 72 | */ 73 | - (NSRange)rangeOfAnchorNamed:(NSString *)anchorName; 74 | 75 | /** 76 | @name Converting to Other Representations 77 | */ 78 | 79 | /** 80 | Encodes the receiver into a generic HTML prepresentation. 81 | 82 | @returns An HTML string. 83 | */ 84 | - (NSString *)htmlString; 85 | 86 | 87 | /** 88 | Converts the receiver into plain text. 89 | 90 | This is different from the `string` method of `NSAttributedString` by also erasing placeholders for text attachments. 91 | 92 | @returns The receiver converted to plain text. 93 | */ 94 | - (NSString *)plainTextString; 95 | 96 | 97 | /** 98 | @name Creating Special Attributed Strings 99 | */ 100 | 101 | 102 | /** 103 | Create a prefix for a paragraph in a list 104 | 105 | @param listCounter The value for the list item. 106 | @param listStyle The list style 107 | @param listIndent The amount in px to indent the list 108 | @param attributes The attribute dictionary for the text to be prefixed 109 | @returns An attributed string with the list prefix 110 | */ 111 | + (NSAttributedString *)prefixForListItemWithCounter:(NSUInteger)listCounter listStyle:(DTCSSListStyle *)listStyle listIndent:(CGFloat)listIndent attributes:(NSDictionary *)attributes; 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /Demo/Resources/iOS6.html: -------------------------------------------------------------------------------- 1 | 2 |

UITextView (iOS 6)

3 |

You can generate attributed strings that work in UIKit, primarily UITextView and UILabel. You have to pass the DTUseiOS6Attributes option to the initWithHTMLData: method to enable iOS 6 compatible attributes. Note that these will only be used if actually running on a device with iOS 6 or higher.

4 |

In the demo app the first tap displays the attributed string with the traditional CoreText and custom attributes in a DTCoreTextAttributedTextView. The new "iOS 6" tab on the right (only visible on iOS 6 or higher) displays the attributed string with iOS 6 compatible attributes in a UITextView." 5 | 6 |

Working Features

7 |

Normal Text, Strike Out, Underlined, Bold, Italic, SmallCapps

8 |

Setting a background color or changing the text color.

9 |

The text-shadow property is supported. Only a single shadow is supported. If multiple shadows are set via CSS then only the first is used.

10 |

Text with Shadow

11 |

Embossed

12 |

Glowing

13 |

Multiple Shadows

16 |

Lists work partially, because we have no access to the private tab stops property of NSParagraphStyle. This is causing some trouble on aligning "hanging" lines.

17 |
    18 |
  • First item
  • 19 |
  • Second item 20 |
  • Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed pulvinar sollicitudin tellus, vitae congue arcu condimentum ut. Fusce sagittis iaculis sem ut bibendum. Sed a ipsum purus, sit amet elementum diam. Fusce sapien sapien, cursus sit amet auctor sit amet, ultricies et lorem.
  • 21 | 22 |
  • You can also nest lists 23 |
      24 |
    • First nested item
    • 25 |
    • Second nested item
    • 26 |
    • Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed pulvinar sollicitudin tellus, vitae congue arcu condimentum ut. Fusce sagittis iaculis sem ut bibendum. Sed a ipsum purus, sit amet elementum diam. Fusce sapien sapien, cursus sit amet auctor sit amet, ultricies et lorem.
    • 27 |
    28 |
  • 29 |
30 |

Known Issues

31 |

Support for changing the line height is broken in iOS 6. As soon as you have more than one font in the attributed string the minimum and maximum line height attributes of the paragraph style are being ignored.

32 | 33 |

Not Supported

34 |

Hyperlinks are not supported. UITextView does not have the NSLink attribute Mac has and there is no delegate protocol to deal with clicking on links.

35 |

Since there is no way to reserve extra space on a glyph there is no way to display custom views or inline images

36 |

Horizontal Rule (hr) is not supported.

37 |

Native lists and text boxes are private to NSParagraphStyle

38 |

The tab stops list is also private in NSParagraphStyle, lists only work by using the first standard tab stop as list indent.

39 | 40 | -------------------------------------------------------------------------------- /Demo/Source/DemoSnippetsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoSnippetsViewController.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Sam Soffes on 1/14/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "DemoSnippetsViewController.h" 10 | #import "DemoTextViewController.h" 11 | 12 | @implementation DemoSnippetsViewController 13 | 14 | #pragma mark NSObject 15 | 16 | - (id)init { 17 | if ((self = [super initWithStyle:UITableViewStyleGrouped])) { 18 | self.title = @"Snippets"; 19 | self.tabBarItem.image = [UIImage imageNamed:@"snippets.png"]; 20 | } 21 | return self; 22 | } 23 | 24 | #pragma mark UIViewController 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | 29 | // Load snippets from plist 30 | NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"Snippets" ofType:@"plist"]; 31 | _snippets = [[NSArray alloc] initWithContentsOfFile:plistPath]; 32 | } 33 | 34 | 35 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { 36 | return YES; 37 | } 38 | 39 | #pragma mark UITableViewDataSource 40 | 41 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 42 | return 1; 43 | } 44 | 45 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 46 | return [_snippets count]; 47 | } 48 | 49 | - (void)configureCell:(DTAttributedTextCell *)cell forIndexPath:(NSIndexPath *)indexPath 50 | { 51 | NSDictionary *snippet = [_snippets objectAtIndex:indexPath.row]; 52 | 53 | NSString *title = [snippet objectForKey:@"Title"]; 54 | NSString *description = [snippet objectForKey:@"Description"]; 55 | 56 | NSString *html = [NSString stringWithFormat:@"

%@

%@

", title, description]; 57 | 58 | [cell setHTMLString:html]; 59 | 60 | cell.attributedTextContextView.shouldDrawImages = YES; 61 | } 62 | 63 | - (DTAttributedTextCell *)tableView:(UITableView *)tableView preparedCellForIndexPath:(NSIndexPath *)indexPath 64 | { 65 | static NSString *cellIdentifier = @"cellIdentifier"; 66 | 67 | if (!cellCache) 68 | { 69 | cellCache = [[NSCache alloc] init]; 70 | } 71 | 72 | // workaround for iOS 5 bug 73 | NSString *key = [NSString stringWithFormat:@"%d-%d", indexPath.section, indexPath.row]; 74 | 75 | DTAttributedTextCell *cell = [cellCache objectForKey:key]; 76 | 77 | if (!cell) 78 | { 79 | // reuse does not work for variable height 80 | //cell = (DTAttributedTextCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 81 | 82 | if (!cell) 83 | { 84 | cell = [[DTAttributedTextCell alloc] initWithReuseIdentifier:cellIdentifier accessoryType:UITableViewCellAccessoryDisclosureIndicator]; 85 | } 86 | 87 | // cache it 88 | [cellCache setObject:cell forKey:key]; 89 | } 90 | 91 | [self configureCell:cell forIndexPath:indexPath]; 92 | 93 | return cell; 94 | } 95 | 96 | // disable this method to get static height = better performance 97 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 98 | { 99 | DTAttributedTextCell *cell = (DTAttributedTextCell *)[self tableView:tableView preparedCellForIndexPath:indexPath]; 100 | 101 | return [cell requiredRowHeightInTableView:tableView]; 102 | } 103 | 104 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 105 | { 106 | DTAttributedTextCell *cell = (DTAttributedTextCell *)[self tableView:tableView preparedCellForIndexPath:indexPath]; 107 | 108 | return cell; 109 | } 110 | 111 | #pragma mark UITableViewDelegate 112 | 113 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 114 | { 115 | NSDictionary *rowSnippet = [_snippets objectAtIndex:indexPath.row]; 116 | 117 | DemoTextViewController *viewController = [[DemoTextViewController alloc] init]; 118 | viewController.fileName = [rowSnippet objectForKey:@"File"]; 119 | viewController.baseURL = [NSURL URLWithString:[rowSnippet objectForKey:@"BaseURL"]]; 120 | 121 | [self.navigationController pushViewController:viewController animated:YES]; 122 | } 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /Core/Source/DTCSSListStyle.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTCSSListStyle.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 8/11/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | /** 10 | List Styles 11 | */ 12 | 13 | typedef enum 14 | { 15 | DTCSSListStyleTypeInherit = 0, 16 | DTCSSListStyleTypeNone, 17 | DTCSSListStyleTypeCircle, 18 | DTCSSListStyleTypeDecimal, 19 | DTCSSListStyleTypeDecimalLeadingZero, 20 | DTCSSListStyleTypeDisc, 21 | DTCSSListStyleTypeSquare, 22 | DTCSSListStyleTypeUpperAlpha, 23 | DTCSSListStyleTypeUpperLatin, 24 | DTCSSListStyleTypeLowerAlpha, 25 | DTCSSListStyleTypeLowerLatin, 26 | DTCSSListStyleTypePlus, 27 | DTCSSListStyleTypeUnderscore, 28 | DTCSSListStyleTypeImage, 29 | DTCSSListStyleTypeInvalid = NSIntegerMax 30 | } DTCSSListStyleType; 31 | 32 | /** 33 | List Marker Positions 34 | */ 35 | typedef enum 36 | { 37 | DTCSSListStylePositionInherit = 0, 38 | DTCSSListStylePositionInside, 39 | DTCSSListStylePositionOutside, 40 | DTCSSListStylePositionInvalid = NSIntegerMax 41 | } DTCSSListStylePosition; 42 | 43 | 44 | /** 45 | This class is the equivalent of `NSTextList` on Mac with the added handling of the marker position. 46 | */ 47 | @interface DTCSSListStyle : NSObject 48 | 49 | 50 | /** 51 | @name Getting Types from Strings 52 | */ 53 | 54 | /** 55 | Convert a string into a list style type. 56 | 57 | @param string The string to convert 58 | */ 59 | + (DTCSSListStyleType)listStyleTypeFromString:(NSString *)string; 60 | 61 | 62 | /** 63 | Convert a string into a marker position. 64 | 65 | @param string The string to convert 66 | */ 67 | + (DTCSSListStylePosition)listStylePositionFromString:(NSString *)string; 68 | 69 | /** 70 | @name Creating List Styles 71 | */ 72 | 73 | /** 74 | Creates a list style from the passed CSS style dictionary 75 | 76 | @param styles A CSS style dictionary from which the construct a suitable list style 77 | */ 78 | - (id)initWithStyles:(NSDictionary *)styles; 79 | 80 | /** 81 | @name Working with CSS Styles 82 | */ 83 | 84 | /** 85 | Update the receiver from the CSS styles dictionary passed 86 | 87 | @param styles A dictionary of CSS styles. 88 | */ 89 | - (void)updateFromStyleDictionary:(NSDictionary *)styles; 90 | 91 | 92 | /** 93 | @name Working with Prefixes 94 | */ 95 | 96 | 97 | /** 98 | Returns the prefix for lists of the receiver's settings. 99 | 100 | @param counter The counter value to use for ordered lists. 101 | @returns The prefix string to prepend to list items. 102 | */ 103 | - (NSString *)prefixWithCounter:(NSInteger)counter; 104 | 105 | 106 | /** 107 | @name Managing Item Numbering 108 | */ 109 | 110 | 111 | /** 112 | Sets the starting item number for the text list. 113 | 114 | The default value is `1`. This value will be used only for ordered lists, and ignored in other cases. 115 | 116 | @param itemNum The item number. 117 | */ 118 | - (void)setStartingItemNumber:(NSInteger)itemNum; 119 | 120 | 121 | /** 122 | Returns the starting item number for the text list. 123 | 124 | The default value is `1`. This value will be used only for ordered lists, and ignored in other cases. 125 | @returns The item number. 126 | */ 127 | - (NSInteger)startingItemNumber; 128 | 129 | 130 | /** 131 | @name Getting Information about Lists 132 | */ 133 | 134 | /** 135 | Returns if the receiver is an ordered or unordered list 136 | 137 | @returns `YES` if the receiver is ordered, `NO` if it is unordered 138 | */ 139 | - (BOOL)isOrdered; 140 | 141 | 142 | /** 143 | If the list style is inherited. 144 | 145 | @warn This is not implemented. 146 | */ 147 | @property (nonatomic, assign) BOOL inherit; 148 | 149 | 150 | /** 151 | The type of the text list 152 | */ 153 | @property (nonatomic, assign) DTCSSListStyleType type; 154 | 155 | 156 | /** 157 | The position of the marker in the prefix. 158 | */ 159 | @property (nonatomic, assign) DTCSSListStylePosition position; 160 | 161 | 162 | /** 163 | The image name to use for the marker 164 | */ 165 | @property (nonatomic, copy) NSString *imageName; 166 | 167 | 168 | @end 169 | -------------------------------------------------------------------------------- /Core/Source/DTAttributedTextCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTAttributedTextCell.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 8/4/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "DTCoreText.h" 10 | #import "DTAttributedTextCell.h" 11 | #import "DTCSSStylesheet.h" 12 | 13 | @implementation DTAttributedTextCell 14 | { 15 | NSAttributedString *_attributedString; 16 | DTAttributedTextContentView *_attributedTextContextView; 17 | 18 | NSUInteger _htmlHash; // preserved hash to avoid relayouting for same HTML 19 | } 20 | 21 | - (id)initWithReuseIdentifier:(NSString *)reuseIdentifier accessoryType:(UITableViewCellAccessoryType)accessoryType 22 | { 23 | self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier]; 24 | if (self) 25 | { 26 | // don't know size jetzt because there's no string in it 27 | _attributedTextContextView = [[DTAttributedTextContentView alloc] initWithFrame:CGRectZero]; 28 | _attributedTextContextView.edgeInsets = UIEdgeInsetsMake(5, 5, 5, 5); 29 | [self.contentView addSubview:_attributedTextContextView]; 30 | } 31 | return self; 32 | } 33 | 34 | 35 | - (void)layoutSubviews 36 | { 37 | [super layoutSubviews]; 38 | 39 | CGFloat neededContentHeight = [self requiredRowHeightInTableView:(UITableView *)self.superview]; 40 | 41 | // after the first call here the content view size is correct 42 | CGRect frame = CGRectMake(0, 0, self.contentView.bounds.size.width, neededContentHeight); 43 | 44 | // only change frame if width has changed to avoid extra layouting 45 | if (_attributedTextContextView.frame.size.width != frame.size.width) 46 | { 47 | _attributedTextContextView.frame = frame; 48 | } 49 | } 50 | 51 | - (void)willMoveToSuperview:(UIView *)newSuperview 52 | { 53 | UITableView *tableView = (UITableView *)newSuperview; 54 | 55 | if (tableView.style == UITableViewStyleGrouped) 56 | { 57 | // need no background because otherwise this would overlap the rounded corners 58 | _attributedTextContextView.backgroundColor = [DTColor clearColor]; 59 | } 60 | 61 | [super willMoveToSuperview:newSuperview]; 62 | } 63 | 64 | - (CGFloat)requiredRowHeightInTableView:(UITableView *)tableView 65 | { 66 | 67 | CGFloat contentWidth = tableView.frame.size.width; 68 | 69 | // reduce width for accessories 70 | switch (self.accessoryType) 71 | { 72 | case UITableViewCellAccessoryDisclosureIndicator: 73 | case UITableViewCellAccessoryCheckmark: 74 | contentWidth -= 20.0f; 75 | break; 76 | case UITableViewCellAccessoryDetailDisclosureButton: 77 | contentWidth -= 33.0f; 78 | break; 79 | case UITableViewCellAccessoryNone: 80 | break; 81 | } 82 | 83 | // reduce width for grouped table views 84 | if (tableView.style == UITableViewStyleGrouped) 85 | { 86 | contentWidth -= 19; 87 | } 88 | 89 | CGSize neededSize = [_attributedTextContextView suggestedFrameSizeToFitEntireStringConstraintedToWidth:contentWidth]; 90 | 91 | // note: non-integer row heights caused trouble < iOS 5.0 92 | return neededSize.height; 93 | } 94 | 95 | #pragma mark Properties 96 | 97 | 98 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated 99 | { 100 | [super setSelected:selected animated:animated]; 101 | 102 | // Configure the view for the selected state 103 | } 104 | 105 | - (void)setHTMLString:(NSString *)html 106 | { 107 | // we don't preserve the html but compare it's hash 108 | NSUInteger newHash = [html hash]; 109 | 110 | if (newHash == _htmlHash) 111 | { 112 | return; 113 | } 114 | 115 | _htmlHash = newHash; 116 | 117 | NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding]; 118 | NSAttributedString *string = [[NSAttributedString alloc] initWithHTMLData:data documentAttributes:NULL]; 119 | self.attributedString = string; 120 | } 121 | 122 | - (void)setAttributedString:(NSAttributedString *)attributedString 123 | { 124 | if (_attributedString != attributedString) 125 | { 126 | _attributedString = attributedString; 127 | 128 | // passthrough 129 | _attributedTextContextView.attributedString = _attributedString; 130 | } 131 | } 132 | 133 | @synthesize attributedString = _attributedString; 134 | @synthesize attributedTextContextView = _attributedTextContextView; 135 | 136 | @end 137 | -------------------------------------------------------------------------------- /Core/Source/DTCoreTextLayouter.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTCoreTextLayouter.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/24/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "DTCoreTextLayouter.h" 10 | 11 | @interface DTCoreTextLayouter () 12 | 13 | @property (nonatomic, strong) NSMutableArray *frames; 14 | @property (nonatomic, assign) dispatch_semaphore_t selfLock; 15 | 16 | - (CTFramesetterRef)framesetter; 17 | - (void)discardFramesetter; 18 | 19 | @end 20 | 21 | #define SYNCHRONIZE_START(obj) dispatch_semaphore_wait(selfLock, DISPATCH_TIME_FOREVER); 22 | #define SYNCHRONIZE_END(obj) dispatch_semaphore_signal(selfLock); 23 | 24 | @implementation DTCoreTextLayouter 25 | { 26 | CTFramesetterRef _framesetter; 27 | 28 | NSAttributedString *_attributedString; 29 | 30 | NSMutableArray *frames; 31 | } 32 | @synthesize selfLock; 33 | 34 | - (id)initWithAttributedString:(NSAttributedString *)attributedString 35 | { 36 | if ((self = [super init])) 37 | { 38 | if (!attributedString) 39 | { 40 | return nil; 41 | } 42 | 43 | selfLock = dispatch_semaphore_create(1); 44 | self.attributedString = attributedString; 45 | } 46 | 47 | return self; 48 | } 49 | 50 | - (void)dealloc 51 | { 52 | SYNCHRONIZE_START(self) // just to be sure 53 | [self discardFramesetter]; 54 | SYNCHRONIZE_END(self) 55 | 56 | dispatch_release(selfLock); 57 | } 58 | 59 | - (NSString *)description 60 | { 61 | return [self.frames description]; 62 | } 63 | 64 | - (NSInteger)numberOfFrames 65 | { 66 | return [self.frames count]; 67 | } 68 | 69 | - (CGSize)suggestedFrameSizeToFitEntireStringConstraintedToWidth:(CGFloat)width 70 | { 71 | // Note: this returns an unreliable measure prior to 4.2 for very long documents 72 | CGSize neededSize = CTFramesetterSuggestFrameSizeWithConstraints(self.framesetter, CFRangeMake(0, 0), NULL, 73 | CGSizeMake(width, CGFLOAT_MAX), 74 | NULL); 75 | 76 | // round up because generally we don't want non-integer view sizes 77 | neededSize.width = ceilf(neededSize.width); 78 | neededSize.height = ceilf(neededSize.height); 79 | 80 | return neededSize; 81 | } 82 | 83 | 84 | // a temporary frame 85 | - (DTCoreTextLayoutFrame *)layoutFrameWithRect:(CGRect)frame range:(NSRange)range 86 | { 87 | DTCoreTextLayoutFrame *newFrame; 88 | @autoreleasepool { 89 | newFrame = [[DTCoreTextLayoutFrame alloc] initWithFrame:frame layouter:self range:range]; 90 | }; 91 | return newFrame; 92 | } 93 | 94 | // reusable frame 95 | - (void)addTextFrameWithFrame:(CGRect)frame 96 | { 97 | DTCoreTextLayoutFrame *newFrame = [self layoutFrameWithRect:frame range:NSMakeRange(0, 0)]; 98 | [self.frames addObject:newFrame]; 99 | } 100 | 101 | - (DTCoreTextLayoutFrame *)layoutFrameAtIndex:(NSInteger)index 102 | { 103 | return [self.frames objectAtIndex:index]; 104 | 105 | } 106 | 107 | #pragma mark Properties 108 | - (CTFramesetterRef)framesetter 109 | { 110 | if (!_framesetter) // Race condition, could be null now but set when we get into the SYNCHRONIZE block - so do the test twice 111 | { 112 | SYNCHRONIZE_START(self) 113 | { 114 | if (!_framesetter) 115 | { 116 | _framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self.attributedString); 117 | } 118 | } 119 | SYNCHRONIZE_END(self) 120 | } 121 | return _framesetter; 122 | } 123 | 124 | 125 | - (void)discardFramesetter 126 | { 127 | { 128 | // framesetter needs to go 129 | if (_framesetter) 130 | { 131 | CFRelease(_framesetter); 132 | _framesetter = NULL; 133 | } 134 | } 135 | } 136 | 137 | - (void)setAttributedString:(NSAttributedString *)attributedString 138 | { 139 | SYNCHRONIZE_START(self) 140 | { 141 | if (_attributedString != attributedString) 142 | { 143 | _attributedString = attributedString; 144 | 145 | [self discardFramesetter]; 146 | } 147 | } 148 | SYNCHRONIZE_END(self) 149 | } 150 | 151 | - (NSAttributedString *)attributedString 152 | { 153 | return _attributedString; 154 | } 155 | 156 | - (NSMutableArray *)frames 157 | { 158 | if (!frames) 159 | { 160 | frames = [[NSMutableArray alloc] init]; 161 | } 162 | 163 | return frames; 164 | } 165 | 166 | 167 | 168 | @synthesize attributedString = _attributedString; 169 | @synthesize frames = _frames; 170 | @synthesize framesetter = _framesetter; 171 | 172 | @end 173 | -------------------------------------------------------------------------------- /Core/Source/default.css: -------------------------------------------------------------------------------- 1 | /* this file is processed with xxd via a build rule and embedded in library */ 2 | 3 | /* these styles come from Safari */ 4 | 5 | /* note that comments are only permitted before selectors and before the styles */ 6 | 7 | /* DO NOT fiddle with this file if you want to have your own styles, 8 | pass your own stylesheet via the option parameter to override these defaults */ 9 | 10 | html 11 | { 12 | /* bla */ 13 | display:block; 14 | } 15 | 16 | head 17 | { 18 | display:none; 19 | } 20 | 21 | title 22 | { 23 | display:none; 24 | } 25 | 26 | style 27 | { 28 | display:none; 29 | } 30 | 31 | body 32 | { 33 | /* safari has a 8 px margin here */ 34 | display:block; 35 | } 36 | 37 | article,aside,footer,header,hgroup,nav,section 38 | { 39 | display:block; 40 | } 41 | 42 | p 43 | { 44 | display:block; 45 | -webkit-margin-before:1em; 46 | -webkit-margin-after:1em; 47 | -webkit-margin-start:0; 48 | -webkit-margin-end:0; 49 | } 50 | 51 | ul,menu,dir 52 | { 53 | display:block; 54 | list-style-type:disc; 55 | -webkit-margin-before:1em; 56 | -webkit-margin-after:1em; 57 | -webkit-margin-start:0; 58 | -webkit-margin-end:0; 59 | -webkit-padding-start:27px; 60 | } 61 | 62 | li 63 | { 64 | display:list-item; 65 | } 66 | 67 | ol 68 | { 69 | display:block; 70 | list-style-type:decimal; 71 | -webkit-margin-before:1em; 72 | -webkit-margin-after:1em; 73 | -webkit-margin-start:0; 74 | -webkit-margin-end:0; 75 | -webkit-padding-start:27px; 76 | } 77 | 78 | ul ul, ol ul 79 | { 80 | list-style-type: circle; 81 | } 82 | 83 | ol ol ul, ol ul ul, ul ol ul, ul ul ul 84 | { 85 | list-style-type: square; 86 | } 87 | 88 | code 89 | { 90 | font-family:Courier; 91 | } 92 | 93 | pre 94 | { 95 | font-family:Courier; 96 | } 97 | 98 | /* color:-webkit-link */ 99 | a 100 | { 101 | color:#0000EE; 102 | text-decoration:underline; 103 | } 104 | 105 | center 106 | { 107 | text-align:center; 108 | display:block; 109 | } 110 | 111 | strong,b 112 | { 113 | font-weight:bolder; 114 | } 115 | 116 | i,em 117 | { 118 | font-style:italic; 119 | } 120 | 121 | u 122 | { 123 | text-decoration:underline; 124 | } 125 | 126 | big 127 | { 128 | font-size:bigger; 129 | } 130 | 131 | small 132 | { 133 | font-size:smaller; 134 | } 135 | 136 | sub 137 | { 138 | font-size:smaller; 139 | vertical-align:sub; 140 | } 141 | 142 | sup 143 | { 144 | font-size:smaller; 145 | vertical-align:super; 146 | } 147 | 148 | s,strike,del 149 | { 150 | text-decoration:line-through; 151 | } 152 | 153 | tt,code,kbd,samp 154 | { 155 | font-family:monospace; 156 | } 157 | 158 | pre,xmp,plaintext,listing 159 | { 160 | display:block; 161 | font-family:monospace; 162 | white-space:pre; 163 | margin-top:1em; 164 | margin-right:0; 165 | margin-bottom:1em; 166 | margin-left:0; 167 | } 168 | 169 | h1 170 | { 171 | display:block; 172 | font-size:2em; 173 | -webkit-margin-before:.67em; 174 | -webkit-margin-after:.67em; 175 | -webkit-margin-start:0; 176 | -webkit-margin-end:0; 177 | font-weight:bold; 178 | } 179 | 180 | h2 181 | { 182 | display:block; 183 | font-size:1.5em; 184 | -webkit-margin-before:.83em; 185 | -webkit-margin-after:.83em; 186 | -webkit-margin-start:0; 187 | -webkit-margin-end:0; 188 | font-weight:bold; 189 | } 190 | 191 | h3 192 | { 193 | display:block; 194 | font-size:1.17em; 195 | -webkit-margin-before:1em; 196 | -webkit-margin-after:1em; 197 | -webkit-margin-start:0; 198 | -webkit-margin-end:0; 199 | font-weight:bold; 200 | } 201 | 202 | h4 203 | { 204 | display:block; 205 | -webkit-margin-before:1.33em; 206 | -webkit-margin-after:1.33em; 207 | -webkit-margin-start:0; 208 | -webkit-margin-end:0; 209 | font-weight:bold; 210 | } 211 | 212 | h5 213 | { 214 | display:block; 215 | font-size:.83em; 216 | -webkit-margin-before:1.67em; 217 | -webkit-margin-after:1.67em; 218 | -webkit-margin-start:0; 219 | -webkit-margin-end:0; 220 | font-weight:bold; 221 | } 222 | 223 | h6 224 | { 225 | display:block; 226 | font-size:.67em; 227 | -webkit-margin-before:2.33em; 228 | -webkit-margin-after:2.33em; 229 | -webkit-margin-start:0; 230 | -webkit-margin-end:0; 231 | font-weight:bold; 232 | } 233 | 234 | div { 235 | display: block; 236 | } 237 | 238 | link { 239 | display: none; 240 | } 241 | 242 | meta { 243 | display: none; 244 | } 245 | 246 | script { 247 | display: none; 248 | } 249 | 250 | hr { 251 | display: block; 252 | -webkit-margin-before: 0.5em; 253 | -webkit-margin-after: 0.5em; 254 | -webkit-margin-start: auto; 255 | -webkit-margin-end: auto; 256 | border-style: inset; 257 | border-width: 1px; 258 | } 259 | 260 | table { 261 | display: table; 262 | border-collapse: separate; 263 | border-spacing: 2px; 264 | border-color: gray; 265 | } -------------------------------------------------------------------------------- /Core/Source/DTCoreTextFontCollection.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTCoreTextFontCollection.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 5/23/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "DTCoreTextFontCollection.h" 10 | #import "DTCoreTextFontDescriptor.h" 11 | 12 | #if TARGET_OS_IPHONE 13 | #import 14 | #elif TARGET_OS_MAC 15 | #import 16 | #endif 17 | 18 | @interface DTCoreTextFontCollection () 19 | 20 | @property (nonatomic, strong) NSArray *fontDescriptors; 21 | @property (nonatomic, strong) NSCache *fontMatchCache; 22 | 23 | @end 24 | 25 | static DTCoreTextFontCollection *_availableFontsCollection = nil; 26 | 27 | 28 | @implementation DTCoreTextFontCollection 29 | { 30 | NSArray *_fontDescriptors; 31 | NSCache *_fontMatchCache; 32 | } 33 | 34 | + (DTCoreTextFontCollection *)availableFontsCollection 35 | { 36 | static dispatch_once_t predicate; 37 | 38 | dispatch_once(&predicate, ^{ 39 | _availableFontsCollection = [[DTCoreTextFontCollection alloc] initWithAvailableFonts]; 40 | }); 41 | 42 | return _availableFontsCollection; 43 | } 44 | 45 | - (id)initWithAvailableFonts 46 | { 47 | self = [super init]; 48 | 49 | if (self) 50 | { 51 | 52 | } 53 | 54 | return self; 55 | } 56 | 57 | 58 | - (DTCoreTextFontDescriptor *)matchingFontDescriptorForFontDescriptor:(DTCoreTextFontDescriptor *)descriptor 59 | { 60 | DTCoreTextFontDescriptor *firstMatch = nil; 61 | NSNumber *cacheKey = [NSString stringWithFormat:@"fontFamily BEGINSWITH[cd] %@ and boldTrait == %d and italicTrait == %d", descriptor.fontFamily, descriptor.boldTrait, descriptor.italicTrait]; 62 | 63 | // try cache 64 | firstMatch = [self.fontMatchCache objectForKey:cacheKey]; 65 | 66 | if (firstMatch) 67 | { 68 | DTCoreTextFontDescriptor *retMatch = [firstMatch copy]; 69 | retMatch.pointSize = descriptor.pointSize; 70 | return retMatch; 71 | } 72 | 73 | // need to search 74 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"fontFamily BEGINSWITH[cd] %@ and boldTrait == %d and italicTrait == %d", descriptor.fontFamily, descriptor.boldTrait, descriptor.italicTrait]; 75 | 76 | NSArray *matchingDescriptors = [self.fontDescriptors filteredArrayUsingPredicate:predicate]; 77 | 78 | //NSLog(@"%@", matchingDescriptors); 79 | 80 | if ([matchingDescriptors count]) 81 | { 82 | firstMatch = [matchingDescriptors objectAtIndex:0]; 83 | [self.fontMatchCache setObject:firstMatch forKey:cacheKey]; 84 | 85 | DTCoreTextFontDescriptor *retMatch = [firstMatch copy]; 86 | 87 | retMatch.pointSize = descriptor.pointSize; 88 | return retMatch; 89 | } 90 | 91 | return nil; 92 | } 93 | 94 | #pragma mark Properties 95 | 96 | - (NSArray *)fontDescriptors 97 | { 98 | if (!_fontDescriptors) 99 | { 100 | // try caches 101 | 102 | NSString *cachesPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"FontDescriptors.cache"]; 103 | 104 | self.fontDescriptors = nil;//[NSKeyedUnarchiver unarchiveObjectWithFile:cachesPath]; 105 | 106 | if (!_fontDescriptors) 107 | { 108 | CTFontCollectionRef fonts = CTFontCollectionCreateFromAvailableFonts(NULL); 109 | 110 | CFArrayRef matchingFonts = CTFontCollectionCreateMatchingFontDescriptors(fonts); 111 | 112 | if (matchingFonts) 113 | { 114 | 115 | // convert all to our objects 116 | NSMutableArray *tmpArray = [[NSMutableArray alloc] init]; 117 | 118 | for (NSInteger i=0; i 2 | 3 | 4 | 5 | 6 | Description 7 | This is the <b>introduction</b> to the <font color="blue" face="Courier">NSAttributedString</font> HTML Additions for iOS. Even this very table view here demonstrates that you can easily add HTML formatted text to table views. 8 | File 9 | README.html 10 | Title 11 | READ ME 12 | 13 | 14 | Description 15 | This demonstrates the features supported by the iOS 6 attributes. 16 | File 17 | iOS6.html 18 | Title 19 | iOS 6 Compatibility Mode 20 | 21 | 22 | Description 23 | Demonstrates CSS styles that are supported so far. 24 | File 25 | styles.html 26 | Title 27 | CSS Styles 28 | 29 | 30 | Description 31 | Demonstrates setting of Line Height 32 | File 33 | LineHeight.html 34 | Title 35 | Line Height 36 | 37 | 38 | Description 39 | Demonstrates really long text. Definitely longer than this meager description. 40 | File 41 | LoremIpsum.html 42 | Title 43 | Lorem Ipsum 44 | 45 | 46 | Description 47 | Demonstrates text alignment via tags and style attributes. 48 | File 49 | Alignment.html 50 | Title 51 | Text Alignment 52 | 53 | 54 | Description 55 | Shows the current level of image support. 56 | File 57 | Image.html 58 | Title 59 | Image Handling 60 | 61 | 62 | Description 63 | Demonstrating support for the HTML5 Video tag when somebody gets around to implement it. 64 | File 65 | Video.html 66 | Title 67 | Video 68 | 69 | 70 | BaseURL 71 | http://apod.nasa.gov/apod/ 72 | Description 73 | HTML handles newlines as spaces, but not always. 74 | File 75 | APOD.html 76 | Title 77 | Space and Newline Handling. 78 | 79 | 80 | Description 81 | Extremely long HTML document for profiling and performance tuning 82 | File 83 | WarAndPeace.html 84 | Title 85 | War & Peace 86 | 87 | 88 | Description 89 | Using Custom Fonts registered in info.plist 90 | File 91 | CustomFont.html 92 | Title 93 | Custom Fonts 94 | 95 | 96 | Description 97 | Custom subviews can be affixed over any glyph run. 98 | File 99 | Subviews.html 100 | Title 101 | Custom Subviews 102 | 103 | 104 | Description 105 | Demo of nested HTML lists 106 | File 107 | ListTest.html 108 | Title 109 | Nested Lists 110 | 111 | 112 | Description 113 | Arabic Ligation 114 | File 115 | ArabicTest.html 116 | Title 117 | Arabic Ligation 118 | 119 | 120 | Description 121 | This demonstrates how adding an object with a given width and height can be replaced with a custom view. 122 | File 123 | Objects.html 124 | Title 125 | Custom Objects 126 | 127 | 128 | Description 129 | Block-level elements can have a background color and padding. 130 | File 131 | TextBoxes.html 132 | Title 133 | Text Boxes 134 | 135 | 136 | Description 137 | Test Cases for Emoji 138 | File 139 | EmojiTest.html 140 | Title 141 | Emoji Test 142 | 143 | 144 | Description 145 | Test Cases for known issues in CoreText 146 | File 147 | CoreTextIssues.html 148 | Title 149 | Known CoreText Issues 150 | 151 | 152 | Description 153 | This is whatever Oliver is currently playing with. Don't touch that! <img src="icon_smile.gif"> 154 | File 155 | CurrentTest.html 156 | Title 157 | Never mind this file ... 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /Core/Test/Source/NSAttributedStringHTMLTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedStringHTMLTest.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Claus Broch on 11/01/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "NSAttributedStringHTMLTest.h" 10 | #import "NSAttributedString+HTML.h" 11 | 12 | #import "NSAttributedString+DTCoreText.h" 13 | 14 | #import "DTHTMLAttributedStringBuilder.h" 15 | 16 | @implementation NSAttributedStringHTMLTest 17 | 18 | - (NSAttributedString *)attributedStringFromHTML:(NSString *)html 19 | { 20 | NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding]; 21 | DTHTMLAttributedStringBuilder*stringBuilder = [[DTHTMLAttributedStringBuilder alloc] initWithHTML:data options:nil documentAttributes:NULL]; 22 | 23 | return [stringBuilder generatedAttributedString]; 24 | } 25 | 26 | - (void)testParagraphs 27 | { 28 | NSString *html = @"Prefix

One\ntwo\n
three

New Paragraph

Suffix"; 29 | NSAttributedString *string = [self attributedStringFromHTML:html]; 30 | 31 | NSData *dump = [[string string] dataUsingEncoding:NSUTF8StringEncoding]; 32 | NSString *resultOnIOS = [dump description]; 33 | 34 | NSString *resultOnMac = @"<50726566 69780a4f 6e652074 776f20e2 80a87468 7265650a 4e657720 50617261 67726170 680a5375 66666978>"; 35 | 36 | STAssertEqualObjects(resultOnIOS, resultOnMac, @"Output on Paragraph Test differs"); 37 | } 38 | 39 | 40 | - (void)testHeaderParagraphs 41 | { 42 | NSString *html = @"Prefix

One

One

One

One

One

New Paragraph

Suffix"; 43 | NSAttributedString *string = [self attributedStringFromHTML:html]; 44 | 45 | NSData *dump = [[string string] dataUsingEncoding:NSUTF8StringEncoding]; 46 | NSString *resultOnIOS = [dump description]; 47 | 48 | NSString *resultOnMac = @"<50726566 69780a4f 6e650a4f 6e650a4f 6e650a4f 6e650a4f 6e650a4e 65772050 61726167 72617068 0a537566 666978>"; 49 | 50 | STAssertEqualObjects(resultOnIOS, resultOnMac, @"Output on Paragraph Test differs"); 51 | } 52 | 53 | 54 | - (void)testListParagraphs 55 | { 56 | NSString *html = @"

Before

  • One
  • Two

After

"; 57 | NSAttributedString *string = [self attributedStringFromHTML:html]; 58 | 59 | NSData *dump = [[string string] dataUsingEncoding:NSUTF8StringEncoding]; 60 | NSString *resultOnIOS = [dump description]; 61 | 62 | NSString *resultOnMac = @"<4265666f 72650a09 e280a209 4f6e650a 09e280a2 0954776f 0a416674 65720a>"; 63 | 64 | STAssertEqualObjects(resultOnIOS, resultOnMac, @"Output on List Test differs"); 65 | } 66 | 67 | - (void)testImageParagraphs 68 | { 69 | NSString *html = @"

Before

Header

after

Some inline text.

"; 70 | NSAttributedString *string = [self attributedStringFromHTML:html]; 71 | 72 | NSData *dump = [[string string] dataUsingEncoding:NSUTF8StringEncoding]; 73 | NSString *resultOnIOS = [dump description]; 74 | 75 | NSString *resultOnMac = @"<4265666f 72650aef bfbc0a48 65616465 720a6166 7465720a 536f6d65 20696e6c 696e6520 efbfbc20 74657874 2e0a>"; 76 | 77 | STAssertEqualObjects(resultOnIOS, resultOnMac, @"Output on List Test differs"); 78 | } 79 | 80 | - (void)testSpaceNormalization 81 | { 82 | NSString *html = @"

Now there is some bold\ntext and spaces\n should be normalized.

"; 83 | NSAttributedString *string = [self attributedStringFromHTML:html]; 84 | 85 | NSData *dump = [[string string] dataUsingEncoding:NSUTF8StringEncoding]; 86 | NSString *resultOnIOS = [dump description]; 87 | 88 | NSString *resultOnMac = @"<4e6f7720 74686572 65206973 20736f6d 6520626f 6c642074 65787420 616e6420 73706163 65732073 686f756c 64206265 206e6f72 6d616c69 7a65642e 0a>"; 89 | 90 | STAssertEqualObjects(resultOnIOS, resultOnMac, @"Output on List Test differs"); 91 | } 92 | 93 | - (void)testSpaceAndNewlines 94 | { 95 | NSString *html = @"bla\nfollows\nNSString *str = @\"The Quick Brown Fox Brown\";"; 96 | NSAttributedString *string = [self attributedStringFromHTML:html]; 97 | 98 | NSData *dump = [[string string] dataUsingEncoding:NSUTF8StringEncoding]; 99 | NSString *resultOnIOS = [dump description]; 100 | 101 | NSString *resultOnMac = @"<626c6120 666f6c6c 6f777320 4e535374 72696e67 202a7374 72203d20 40225468 65205175 69636b20 42726f77 6e20466f 78204272 6f776e22 3b>"; 102 | 103 | STAssertEqualObjects(resultOnIOS, resultOnMac, @"Output on List Test differs"); 104 | } 105 | 106 | - (void)testMissingClosingTagAndSpacing 107 | { 108 | NSString *html = @"image \n last"; 109 | NSAttributedString *string = [self attributedStringFromHTML:html]; 110 | 111 | NSData *dump = [[string string] dataUsingEncoding:NSUTF8StringEncoding]; 112 | NSString *resultOnIOS = [dump description]; 113 | 114 | NSString *resultOnMac = @"<696d6167 65206c61 7374>"; 115 | 116 | STAssertEqualObjects(resultOnIOS, resultOnMac, @"Output on Invalid Tag Test differs"); 117 | 118 | } 119 | 120 | 121 | - (void)testAttributedStringColorToHTML 122 | { 123 | NSMutableAttributedString *string = [[NSMutableAttributedString alloc]initWithString: @"test"]; 124 | 125 | UIColor *color = [ UIColor colorWithRed: 1.0 green: 0.0 blue: 0.0 alpha: 1.0 ]; 126 | 127 | [ string setAttributes: [ NSDictionary dictionaryWithObject: (id)color.CGColor forKey: (id)kCTForegroundColorAttributeName ] range: NSMakeRange(0, 2) ]; 128 | 129 | NSString *expected = @"test\n"; 130 | 131 | STAssertEqualObjects([ string htmlString ], expected, @"Output on HTML string color test differs"); 132 | } 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /Core/Source/DTColor+HTML.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTColor+HTML.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/9/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #if TARGET_OS_IPHONE 10 | 11 | /** 12 | Methods used to work with HTML representations of colors. 13 | */ 14 | @interface UIColor (HTML) 15 | 16 | /** 17 | Takes a CSS color string ('333', 'F9FFF9'), determines the RGB values used, and returns a UIColor object of that color. 18 | For each part of the RGB color those numbers for that color are converted to a number using a category on NSString. Then that number is divided by the maximum value, 15 for 3 character strings and 255 for 6 character strings, making the color a percentage and within the range 0.0 and 1.0 that UIColor uses. 19 | @param hex A CSS hexadecimal color string of length 6 or 3. 20 | @returns A UIColor object generated from the hexadecimal color string with alpha 1.0. 21 | */ 22 | + (UIColor *)colorWithHexString:(NSString *)hex; 23 | 24 | /** 25 | Takes an English string representing a color and maps it to a numeric RGB value as declared by the HTML and CSS specifications (see http://www.w3schools.com/html/html_colornames.asp). Also accepts CSS `#` hexadecimal colors, `rgba`, and `rgb` and does the right thing returning a corresponding UIColor. 26 | If a color begins with a `#` we know that it is a hexadecimal color and send it to colorWithHexString:. If the string is an `rgba()` color declaration the comma delimited r, g, b, and a values are made into percentages and then made into a UIColor which is returned. If the string is an `rgb()` color declaration the same process happens except with an alpha of 1.0. 27 | The last case is that the color string is not a numeric declaration `#`, nor a `rgba` or `rgb` declaration so the CSS color value matching the English string is found in a lookup dictionary and then passed to colorWithHexString: which will make a UIColor out of the hexadecimal string. 28 | @param name The CSS color string that we want to map from a name into an RGB color. 29 | @returns A UIColor object representing the name parameter as numeric values declared by the HTML and CSS specifications, a `rgba()` color, or a `rgb()` color. 30 | */ 31 | + (UIColor *)colorWithHTMLName:(NSString *)name; 32 | 33 | /** 34 | Return a string hexadecimal representation of this UIColor. Splits the color into components with CGColor methods, re-maps them from percentages to the range 0-255, and depending on the number of components returns a grayscale (repeating string of two characters) or color RGB (alpha is stripped) six character string. In the event of a non-2 or non-4 component color nil is returned as it is from an unsupported color space. 35 | @returns A CSS hexadecimal NSString specifying this UIColor. 36 | */ 37 | - (NSString *)htmlHexString; 38 | 39 | 40 | /** 41 | A quick method to return the alpha component of this UIColor by using the CGColorGetAlpha method. 42 | @returns The floating point alpha value of this UIColor. 43 | */ 44 | - (CGFloat)alphaComponent; 45 | 46 | @end 47 | 48 | #else 49 | 50 | /** 51 | Methods used to work with HTML representations of colors. 52 | */ 53 | @interface NSColor (HTML) 54 | 55 | 56 | /** 57 | Takes a CSS color string ('333', 'F9FFF9'), determines the RGB values used, and returns an NSColor object of that color. 58 | For each part of the RGB color those numbers for that color are converted to a number using a category on NSString. Then that number is divided by the maximum value, 15 for 3 character strings and 255 for 6 character strings, making the color a percentage and within the range 0.0 and 1.0 that NSColor uses. 59 | @param hex A CSS hexadecimal color string of length 6 or 3. 60 | @returns An NSColor object generated from the hexadecimal color string with alpha 1.0. 61 | */ 62 | + (NSColor *)colorWithHexString:(NSString *)hex; 63 | 64 | 65 | /** 66 | Takes an English string representing a color and maps it to a numeric RGB value as declared by the HTML and CSS specifications (see http://www.w3schools.com/html/html_colornames.asp). Also accepts CSS `#` hexadecimal colors, `rgba`, and `rgb` and does the right thing returning a corresponding NSColor. 67 | If a color begins with a `#` we know that it is a hexadecimal color and send it to colorWithHexString:. If the string is an `rgba()` color declaration the comma delimited r, g, b, and a values are made into percentages and then made into an NSColor which is returned. If the string is an `rgb()` color declaration the same process happens except with an alpha of 1.0. 68 | The last case is that the color string is not a numeric declaration `#`, nor a `rgba` or `rgb` declaration so the CSS color value matching the English string is found in a lookup dictionary and then passed to colorWithHexString: which will make an NSColor out of the hexadecimal string. 69 | @param name The CSS color string that we want to map from a name into an RGB color. 70 | @returns An NSColor object representing the name parameter as numeric values declared by the HTML and CSS specifications, a `rgba()` color, or a `rgb()` color. 71 | */ 72 | + (NSColor *)colorWithHTMLName:(NSString *)name; 73 | 74 | 75 | /** 76 | Return a string hexadecimal representation of this NSColor. Splits the color into components with CGColor methods, re-maps them from percentages in the range 0-255, and returns the RGB color (alpha is stripped) in a six character string. 77 | @returns A CSS hexadecimal NSString specifying this NSColor. 78 | */ 79 | - (NSString *)htmlHexString; 80 | 81 | #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_7 82 | /** 83 | Converts a CGColorRef into an NSColor by placing each component into an NSColor and pending on the component count to return a grayscale or rgb color. If there are not 2 (grayscale) or 4 (rgba) components the color is from an unsupported color space and nil is returned. 84 | @param cgColor The CGColorRef to convert 85 | @returns An NSColor of this CGColorRef 86 | */ 87 | + (NSColor *)colorWithCGColor:(CGColorRef)cgColor; 88 | 89 | /** 90 | Converts an NSColor into a CGColorRef. 91 | @returns A CGColorRef of this NSColor 92 | */ 93 | - (CGColorRef)CGColor; 94 | #endif 95 | 96 | @end 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /Core/Source/DTAttributedTextContentView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TextView.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/9/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class DTAttributedTextContentView; 12 | @class DTCoreTextLayoutFrame; 13 | @class DTTextBlock; 14 | @class DTCoreTextLayouter; 15 | @class DTTextAttachment; 16 | 17 | /** 18 | Protocol to provide custom views for elements in an DTAttributedTextContentView. Also the delegate gets notified once the text view has been drawn. 19 | */ 20 | @protocol DTAttributedTextContentViewDelegate 21 | 22 | @optional 23 | 24 | /** 25 | @name Notifications 26 | */ 27 | 28 | 29 | /** 30 | Called after a layout frame or a part of it is drawn. 31 | 32 | @param attributedTextContentView The content view that drew a layout frame 33 | @param layoutFrame The layout frame that was drawn for 34 | @param context The graphics context that was drawn into 35 | */ 36 | - (void)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView didDrawLayoutFrame:(DTCoreTextLayoutFrame *)layoutFrame inContext:(CGContextRef)context; 37 | 38 | 39 | /** 40 | Called before the text belonging to a text block is drawn. 41 | 42 | This gives the developer an opportunity to draw a custom background below a text block. 43 | 44 | @param attributedTextContentView The content view that drew a layout frame 45 | @param textBlock The text block 46 | @param rect The frame within the content view's coordinate system that will be drawn into 47 | @param context The graphics context that will be drawn into 48 | @param layoutFrame The layout frame that will be drawn for 49 | @param returns `YES` is the standard fill of the text block should be drawn, `NO` if it should not 50 | */ 51 | - (BOOL)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView shouldDrawBackgroundForTextBlock:(DTTextBlock *)textBlock frame:(CGRect)frame context:(CGContextRef)context forLayoutFrame:(DTCoreTextLayoutFrame *)layoutFrame; 52 | 53 | /** 54 | @name Providing Custom Views for Content 55 | */ 56 | 57 | 58 | /** 59 | Provide custom view for an attachment, e.g. an imageView for images 60 | 61 | @param attributedTextContentView The content view asking for a custom view 62 | @param attachment The that this view should represent 63 | @param frame The frame that the view should use to fit on top of the space reserved for the attachment 64 | @returns The view that should represent the given attachment 65 | */ 66 | - (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView viewForAttachment:(DTTextAttachment *)attachment frame:(CGRect)frame; 67 | 68 | 69 | /** 70 | Provide button to be placed over links, the identifier is used to link multiple parts of the same A tag 71 | 72 | @param attributedTextContentView The content view asking for a custom view 73 | @param url The `NSURL` of the hyperlink 74 | @param identifier An identifier that uniquely identifies the hyperlink within the document 75 | @param frame The frame that the view should use to fit on top of the space reserved for the attachment 76 | @returns The view that should represent the given hyperlink 77 | */ 78 | - (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView viewForLink:(NSURL *)url identifier:(NSString *)identifier frame:(CGRect)frame; 79 | 80 | 81 | /** 82 | Provide generic views for all attachments. 83 | 84 | This is only called if the more specific delegate methods are not implemented. 85 | 86 | @param attributedTextContentView The content view asking for a custom view 87 | @param string The attributed sub-string containing this element 88 | @param frame The frame that the view should use to fit on top of the space reserved for the attachment 89 | @returns The view that should represent the given hyperlink or text attachment 90 | @see attributedTextContentView:viewForAttachment:frame: and attributedTextContentView:viewForAttachment:frame: 91 | */ 92 | - (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView viewForAttributedString:(NSAttributedString *)string frame:(CGRect)frame; 93 | 94 | @end 95 | 96 | 97 | 98 | @interface DTAttributedTextContentView : UIView 99 | { 100 | NSAttributedString *_attributedString; 101 | DTCoreTextLayoutFrame *_layoutFrame; 102 | 103 | UIEdgeInsets _edgeInsets; 104 | 105 | NSMutableDictionary *customViewsForAttachmentsIndex; 106 | } 107 | 108 | - (id)initWithAttributedString:(NSAttributedString *)attributedString width:(CGFloat)width; 109 | 110 | - (void)layoutSubviewsInRect:(CGRect)rect; 111 | - (void)relayoutText; 112 | - (void)removeAllCustomViews; 113 | - (void)removeAllCustomViewsForLinks; 114 | 115 | - (CGSize)attributedStringSizeThatFits:(CGFloat)width; 116 | - (CGSize)suggestedFrameSizeToFitEntireStringConstraintedToWidth:(CGFloat)width; // obeys the edge insets 117 | 118 | // properties are overwritten with locking to avoid problem with async drawing 119 | @property (atomic, strong) DTCoreTextLayouter *layouter; 120 | @property (atomic, strong) DTCoreTextLayoutFrame *layoutFrame; 121 | 122 | @property (nonatomic, strong) NSMutableSet *customViews; 123 | 124 | @property (nonatomic, copy) NSAttributedString *attributedString; 125 | @property (nonatomic) UIEdgeInsets edgeInsets; 126 | @property (nonatomic) BOOL drawDebugFrames; 127 | @property (nonatomic) BOOL shouldDrawImages; 128 | @property (nonatomic) BOOL shouldDrawLinks; 129 | @property (nonatomic) BOOL shouldLayoutCustomSubviews; 130 | @property (nonatomic) CGPoint layoutOffset; 131 | @property (nonatomic) CGSize backgroundOffset; 132 | 133 | @property (nonatomic, assign) IBOutlet id delegate; // subtle simulator bug - use assign not __unsafe_unretained 134 | 135 | @property (nonatomic, assign) dispatch_semaphore_t selfLock; 136 | 137 | 138 | @end 139 | 140 | 141 | @interface DTAttributedTextContentView (Tiling) 142 | 143 | + (void)setLayerClass:(Class)layerClass; 144 | + (Class)layerClass; 145 | 146 | @end 147 | 148 | -------------------------------------------------------------------------------- /Readme.markdown: -------------------------------------------------------------------------------- 1 | DTCoreText 2 | ========== 3 | 4 | This project aims to duplicate the methods present on Mac OSX which allow creation of `NSAttributedString` from HTML code on iOS. Previously we referred to it as NSAttributedString+HTML (or NSAS+HTML in short) but this only covers about half of what this framework does. 5 | 6 | Please support us so that we can continue to make DTCoreText even more awesome! 7 | 8 | 9 | PayPal - The safer, easier way to pay online! 10 | 11 | 12 | The project covers two broad areas: 13 | 14 | 1. Layouting - Interfacing with CoreText, generating NSAttributedString instances from HTML code 15 | 2. UI - several UI-related classes render these objects 16 | 17 | This is useful for drawing simple rich text like any HTML document without having to use a `UIWebView`. 18 | 19 | Please read the [Q&A](http://www.cocoanetics.com/2011/08/nsattributedstringhtml-qa/). 20 | 21 | Your help is much appreciated. Please send pull requests for useful additions you make or ask me what work is required. 22 | 23 | If you find brief test cases where the created `NSAttributedString` differs from the version on OSX please send them to us! 24 | 25 | Follow [@cocoanetics](http://twitter.com/cocoanetics) on Twitter. 26 | 27 | License 28 | ------- 29 | 30 | It is open source and covered by a standard BSD license. That means you have to mention *Cocoanetics* as the original author of this code. You can purchase a Non-Attribution-License from us. 31 | 32 | Documentation 33 | ------------- 34 | 35 | Documentation can be [browsed online](http://cocoanetics.github.com/DTCoreText) or installed in your Xcode Organizer via the [Atom Feed URL](http://cocoanetics.github.com/DTCoreText/DTCoreText.atom). 36 | 37 | Requirements 38 | ------------ 39 | 40 | DTCoreText needs a minimum iOS deployment target of iOS 4.2 because of: 41 | 42 | - NSCache 43 | - GCD-based threading and locking 44 | - Blocks 45 | - ARC 46 | 47 | DTCoreText is designed to be included as static library from a subproject, it embeds several classes from the DTFoundation project. If you want to use DTFoundation as well in your project you need to use the "Static Library (no DTFoundation)" target to avoid duplicate symbols. 48 | 49 | Setup 50 | ----- 51 | 52 | The best way to use DTCoreText is to add it in Xcode as a subproject of your project with the following steps. 53 | 54 | 1. Make DTCoreText a git submodule of your project 55 | 56 | `git submodule add https://github.com/Cocoanetics/DTCoreText.git Externals/DTCoreText` 57 | 58 | 2. DTCoreText uses DTHTMLParser and DTVersion from DTFoundation which is set up as a git submodule in Core/Externals/DTFoundation, so you need to get these files as well 59 | 60 | `git submodule update --init --recursive` 61 | 62 | 3. Open the destination project and create an "Externals" group. 63 | 64 | 4. Add files… or drag `DTCoreText.xcodeproj` to the Externals group 65 | 66 | 5. In your application target's Build Phases: Target Dependencies add the `Static Library` from the DTCoreText sub project 67 | 68 | 6. In your application target's Build Phases: Link Binary With Libraries phase add the following: 69 | 70 | libDTCoreText.a (target from the DTCoreText sub-project) 71 | libxml2.dylib 72 | ImageIO.framework 73 | QuartzCore.framework 74 | CoreText.framework 75 | MobileCoreServices.framework 76 | 77 | 7. Go to File: Project Settings… and change the derived data location to project-relative. 78 | 79 | 8. Add the DerivedData folder to your git ignore. 80 | 81 | 9. In your application's target Build Settings: 82 | - Add `$(PROJECT_DIR)` to `User Header Search Paths`, set to `recursive` 83 | - Set `Always Search User Paths` to `Yes`. 84 | - Add the `-ObjC` flag to your app target's `Other Linker Flags` 85 | 86 | OPTIONAL LINKER SETTINGS: 87 | - If you find that your app crashes with an unrecognized selector from one of this library's categories, you might also need the `-all_load linker` flag. Alternatively you can use `-force-load` with the full path to the static library. This causes the linker to load all categories from the static library. 88 | - If your app does not use ARC yet (but DTCoreText does) then you also need the `-fobjc-arc` linker flag. 89 | 90 | Known Issues 91 | ------------ 92 | 93 | CoreText has a problem prior to iOS 5 where it takes around a second on device to initialize its internal font lookup table. You have two workarounds available: 94 | 95 | - trigger the loading on a background thread like shown in http://www.cocoanetics.com/2011/04/coretext-loading-performance/ 96 | - if you only use certain fonts then add the variants to the DTCoreTextFontOverrides.plist, this speeds up the finding of a specific font face from the font family 97 | 98 | Some combinations of fonts and unusual list types cause an extra space to appear. e.g. 20 px Courier + Circle 99 | 100 | In many aspects DTCoreText is superior to the Mac version of generating NSAttributedStrings from HTML. These become apparent in the MacUnitTest where the output from both is directly compared. I am summarizing them here for references. 101 | 102 | In the following "Mac" means the initWithHTML: methods there, "DTCoreText" means DTCoreText's initWithHTML and/or DTHTMLAttributedStringBuilder. 103 | 104 | - Mac does not support the video tag, DTCoreText does. 105 | - DTCoreText is able to synthesize small caps by putting all characters in upper case and using a second smaller font for lowercase characters. 106 | - I suspect that Mac makes use of the -webkit-margin-* CSS styles for spacing the paragraphs, DTCoreText only uses the -webkit-margin-bottom and margin-bottom at present. 107 | - Mac supports CSS following addresses, e.g. "ul ul" to change the list style for stacked lists. DTCoreText does not support that and so list bullets stay the same for multiple levels. 108 | - Mac outputs newlines in PRE tags as \n, iOS replaces these with Unicode Line Feed characters so that the paragraph spacing is applied at the end of the PRE tag, not after each line. (iOS wraps code lines when layouting) 109 | - Mac does not properly encode a double list start. iOS prints the empty list prefix. 110 | - Mac seems to ignore list-style-position:outside, iOS does the right thing. 111 | 112 | If you find an issue then you are welcome to fix it and contribute your fix via a GitHub pull request. 113 | -------------------------------------------------------------------------------- /Core/Source/DTAttributedTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTAttributedTextView.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/12/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "DTAttributedTextView.h" 10 | #import "DTCoreText.h" 11 | 12 | @interface DTAttributedTextView () 13 | 14 | - (void)setup; 15 | 16 | @end 17 | 18 | 19 | 20 | @implementation DTAttributedTextView 21 | 22 | - (id)initWithFrame:(CGRect)frame 23 | { 24 | self = [super initWithFrame:frame]; 25 | 26 | if (self) 27 | { 28 | [self setup]; 29 | } 30 | 31 | return self; 32 | } 33 | 34 | - (void)dealloc 35 | { 36 | contentView.delegate = nil; 37 | [contentView removeObserver:self forKeyPath:@"frame"]; 38 | } 39 | 40 | - (void)layoutSubviews 41 | { 42 | [super layoutSubviews]; 43 | 44 | [self contentView]; 45 | 46 | // layout custom subviews for visible area 47 | [contentView layoutSubviewsInRect:self.bounds]; 48 | } 49 | 50 | - (void)awakeFromNib 51 | { 52 | [self setup]; 53 | } 54 | 55 | // default 56 | - (void)setup 57 | { 58 | if (!self.backgroundColor) 59 | { 60 | self.backgroundColor = [DTColor whiteColor]; 61 | self.opaque = YES; 62 | return; 63 | } 64 | 65 | CGFloat alpha = [self.backgroundColor alphaComponent]; 66 | 67 | if (alpha < 1.0) 68 | { 69 | self.opaque = NO; 70 | self.contentView.opaque = NO; 71 | } 72 | else 73 | { 74 | self.opaque = YES; 75 | self.contentView.opaque = YES; 76 | } 77 | 78 | self.autoresizesSubviews = YES; 79 | self.clipsToBounds = YES; 80 | } 81 | 82 | // override class e.g. for mutable content view 83 | - (Class)classForContentView 84 | { 85 | return [DTAttributedTextContentView class]; 86 | } 87 | 88 | #pragma mark External Methods 89 | - (void)scrollToAnchorNamed:(NSString *)anchorName animated:(BOOL)animated 90 | { 91 | NSRange range = [self.contentView.attributedString rangeOfAnchorNamed:anchorName]; 92 | 93 | if (range.length != NSNotFound) 94 | { 95 | // get the line of the first index of the anchor range 96 | DTCoreTextLayoutLine *line = [self.contentView.layoutFrame lineContainingIndex:range.location]; 97 | 98 | // make sure we don't scroll too far 99 | CGFloat maxScrollPos = self.contentSize.height - self.bounds.size.height + self.contentInset.bottom + self.contentInset.top; 100 | CGFloat scrollPos = MIN(line.frame.origin.y, maxScrollPos); 101 | 102 | // scroll 103 | [self setContentOffset:CGPointMake(0, scrollPos) animated:animated]; 104 | } 105 | } 106 | 107 | #pragma mark Notifications 108 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 109 | { 110 | if (object == contentView && [keyPath isEqualToString:@"frame"]) 111 | { 112 | CGRect newFrame = [[change objectForKey:NSKeyValueChangeNewKey] CGRectValue]; 113 | self.contentSize = newFrame.size; 114 | } 115 | } 116 | 117 | #pragma mark Properties 118 | - (DTAttributedTextContentView *)contentView 119 | { 120 | if (!contentView) 121 | { 122 | // subclasses can specify a DTAttributedTextContentView subclass instead 123 | Class classToUse = [self classForContentView]; 124 | 125 | contentView = [[classToUse alloc] initWithFrame:self.bounds]; 126 | contentView.userInteractionEnabled = YES; 127 | contentView.backgroundColor = self.backgroundColor; 128 | contentView.shouldLayoutCustomSubviews = NO; // we call layout when scrolling 129 | 130 | // we want to know if the frame changes so that we can adjust the scrollview content size 131 | [contentView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil]; 132 | 133 | [self addSubview:contentView]; 134 | } 135 | 136 | return contentView; 137 | } 138 | 139 | - (void)setBackgroundColor:(DTColor *)newColor 140 | { 141 | if ([newColor alphaComponent]<1.0) 142 | { 143 | super.backgroundColor = newColor; 144 | contentView.backgroundColor = [DTColor clearColor]; 145 | self.opaque = NO; 146 | } 147 | else 148 | { 149 | super.backgroundColor = newColor; 150 | 151 | if (contentView.opaque) 152 | { 153 | contentView.backgroundColor = newColor; 154 | } 155 | } 156 | } 157 | 158 | - (UIView *)backgroundView 159 | { 160 | if (!backgroundView) 161 | { 162 | backgroundView = [[UIView alloc] initWithFrame:self.bounds]; 163 | backgroundView.backgroundColor = [DTColor whiteColor]; 164 | 165 | // default is no interaction because background should have no interaction 166 | backgroundView.userInteractionEnabled = NO; 167 | 168 | [self insertSubview:backgroundView belowSubview:self.contentView]; 169 | 170 | // make content transparent so that we see the background 171 | contentView.backgroundColor = [DTColor clearColor]; 172 | contentView.opaque = NO; 173 | } 174 | 175 | return backgroundView; 176 | } 177 | 178 | - (void)setBackgroundView:(UIView *)newBackgroundView 179 | { 180 | if (backgroundView != newBackgroundView) 181 | { 182 | [backgroundView removeFromSuperview]; 183 | backgroundView = newBackgroundView; 184 | 185 | [self insertSubview:backgroundView belowSubview:self.contentView]; 186 | 187 | if (backgroundView) 188 | { 189 | // make content transparent so that we see the background 190 | contentView.backgroundColor = [DTColor clearColor]; 191 | contentView.opaque = NO; 192 | } 193 | else 194 | { 195 | contentView.backgroundColor = [DTColor whiteColor]; 196 | contentView.opaque = YES; 197 | } 198 | } 199 | } 200 | 201 | - (void)setAttributedString:(NSAttributedString *)string 202 | { 203 | self.contentView.attributedString = string; 204 | 205 | // might need layout for visible custom views 206 | [self setNeedsLayout]; 207 | 208 | // adjust content size right away 209 | self.contentSize = self.contentView.frame.size; 210 | } 211 | 212 | - (NSAttributedString *)attributedString 213 | { 214 | return self.contentView.attributedString; 215 | } 216 | 217 | 218 | - (void)setFrame:(CGRect)frame 219 | { 220 | if (!CGRectEqualToRect(self.frame, frame)) 221 | { 222 | if (self.frame.size.width != frame.size.width) 223 | { 224 | contentView.frame = CGRectMake(0,0,frame.size.width, frame.size.height); 225 | } 226 | [super setFrame:frame]; 227 | } 228 | } 229 | 230 | - (void)setTextDelegate:(id)aTextDelegate 231 | { 232 | self.contentView.delegate = aTextDelegate; 233 | } 234 | 235 | - (id)textDelegate 236 | { 237 | return contentView.delegate; 238 | } 239 | 240 | @synthesize attributedString; 241 | @synthesize contentView; 242 | @synthesize textDelegate; 243 | 244 | @end 245 | -------------------------------------------------------------------------------- /Demo/Resources/CustomFont.html: -------------------------------------------------------------------------------- 1 |

Fonts

2 |

A font can either be specified directly via its face or via CSS styles. In CSS the font is never specified directly but instead via the font-family and various attributes.

3 |

Standard Fonts

4 |

The Standard font family to be used is 'Times New Roman', 12 px.

5 |

These are the fonts where all 4 basic faces are available: Regular, Bold, Italic and Bold/Italic.

6 | 7 |

Arial

8 |
    9 |
  • Regular:
    ArialMT
  • 10 |
  • Bold:
    Arial-BoldMT
  • 11 |
  • Italic:
    Arial-ItalicMT
  • 12 |
  • Bold/Italic:
    Arial-BoldItalicMT
  • 13 |
14 |

Baskerville

15 |
    16 |
  • Regular:
    Baskerville
  • 17 |
  • Bold:
    Baskerville-SemiBold
  • 18 |
  • Italic:
    Baskerville-Italic
  • 19 |
  • Bold/Italic:
    Baskerville-SemiBoldItalic
  • 20 |
21 |

Cochin

22 |
    23 |
  • Regular:
    Cochin
  • 24 |
  • Bold:
    Cochin-Bold
  • 25 |
  • Italic:
    Cochin-Italic
  • 26 |
  • Bold/Italic:
    Cochin-BoldItalic
  • 27 |
28 |

Courier

29 |
    30 |
  • Regular:
    Courier
  • 31 |
  • Bold:
    Courier-Bold
  • 32 |
  • Italic:
    Courier-Oblique
  • 33 |
  • Bold/Italic:
    Courier-BoldOblique
  • 34 |
35 |

Courier New

36 |
    37 |
  • Regular:
    CourierNewPSMT
  • 38 |
  • Bold:
    CourierNewPS-BoldMT
  • 39 |
  • Italic:
    CourierNewPS-ItalicMT
  • 40 |
  • Bold/Italic:
    CourierNewPS-BoldItalicMT
  • 41 |
42 |

Georgia

43 |
    44 |
  • Regular:
    Georgia
  • 45 |
  • Bold:
    Georgia-Bold
  • 46 |
  • Italic:
    Georgia-Italic
  • 47 |
  • Bold/Italic:
    Georgia-BoldItalic
  • 48 |
49 |

Helvetica

50 |
    51 |
  • Regular:
    Helvetica
  • 52 |
  • Bold:
    Helvetica-Bold
  • 53 |
  • Italic:
    Helvetica-Oblique
  • 54 |
  • Bold/Italic:
    Helvetica-BoldOblique
  • 55 |
56 |

Helvetica Neue

57 |
    58 |
  • Regular:
    HelveticaNeue
  • 59 |
  • Bold:
    HelveticaNeue-Bold
  • 60 |
  • Italic:
    HelveticaNeue-Italic
  • 61 |
  • Bold/Italic:
    HelveticaNeue-BoldItalic
  • 62 |
63 |

Palatino

64 |
    65 |
  • Regular:
    Palatino-Roman
  • 66 |
  • Bold:
    Palatino-Bold
  • 67 |
  • Italic:
    Palatino-Italic
  • 68 |
  • Bold/Italic:
    Palatino-BoldItalic
  • 69 |
70 |

Times New Roman

71 |
    72 |
  • Regular:
    TimesNewRomanPSMT
  • 73 |
  • Bold:
    TimesNewRomanPS-BoldMT
  • 74 |
  • Italic:
    TimesNewRomanPS-ItalicMT
  • 75 |
  • Bold/Italic:
    TimesNewRomanPS-BoldItalicMT
  • 76 |
77 |

Trebuchet MS

78 |
    79 |
  • Regular:
    TrebuchetMS
  • 80 |
  • Bold:
    TrebuchetMS-Bold
  • 81 |
  • Italic:
    TrebuchetMS-Italic
  • 82 |
  • Bold/Italic:
    Trebuchet-BoldItalic
  • 83 |
84 |

Verdana

85 |
    86 |
  • Regular:
    Verdana
  • 87 |
  • Bold:
    Verdana-Bold
  • 88 |
  • Italic:
    Verdana-Italic
  • 89 |
  • Bold/Italic:
    Verdana-BoldItalic
  • 90 |
91 |
92 |

Custom Fonts

93 |

Usage of custom fonts is supported. You have to register these in the info.plist.

94 |

Note: For some fonts you might have to bundle the normal as well as the bold variant for CTFontDescriptor to find the normal font.

95 |

Core Text automatically display Right-to-Left Text as such if only RTL characters are used 96 |

XB Niloofar:

97 |

بِسْمِ اللّٰهِ

98 |

American Typewriter:

99 |

AmericanTypewriter

100 |

American Typewriter Bold:

101 |

AmericanTypewriter

102 | 103 |
104 |

Font Variants

105 |

The font variant 'Small Caps' is supported by synthesizing an attributed string that has the lower case characters in the same font as the upper case ones, but 30% smaller.

106 |

Helvetica (synthesized):
The Quick Brown Fox Jumps ...

107 |

HoeflerText (native, iPad only):
The Quick Brown Fox Jumps ...

Supported values for the style font-variant:

108 |
  • inherit
  • normal
  • small-caps
-------------------------------------------------------------------------------- /Demo/Resources/ListTest.html: -------------------------------------------------------------------------------- 1 |

List Styles

2 |

A variety of list styles are supported.

3 |
    4 |
  1. First
  2. 5 |
  3. Second
  4. 6 |
  5. Third
  6. 7 |
8 | 9 |

Nesting

10 |

Nesting is supported.

11 | 12 |

Unordered Lists

13 |
    14 |
  • First item
  • 15 |
  • Second item 16 |
      17 |
    • Nested unordered lists
    • 18 |
    • Nested line 2
    • 19 |
    • Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed pulvinar sollicitudin tellus, vitae congue arcu condimentum ut. Fusce sagittis iaculis sem ut bibendum. Sed a ipsum purus, sit amet elementum diam. Fusce sapien sapien, cursus sit amet auctor sit amet, ultricies et lorem. Pellentesque dignissim gravida nunc, id porta felis dapibus nec. Proin ultrices, dui vel tempor imperdiet, mi libero congue orci, at auctor ante quam et lorem. Mauris imperdiet molestie leo at pellentesque. Maecenas nec nulla sed felis tristique sagittis. Suspendisse sit amet lectus vitae felis ultrices interdum vel nec ipsum. Proin elementum lectus sed nisi varius id euismod mauris lobortis. Phasellus eget purus ut nunc vulputate tincidunt sed ac purus. Aenean iaculis eros sed odio faucibus commodo. Morbi eu metus risus, ut convallis urna. Cras aliquet urna non diam rhoncus posuere. Aliquam est risus, ullamcorper ac malesuada eget, molestie vitae mauris. Nam volutpat, eros at scelerisque gravida, neque lorem interdum sapien, sed sollicitudin odio neque condimentum lacus. Curabitur congue tortor quis quam mattis lobortis. Quisque a ligula ipsum, eget hendrerit elit. Ut nec nibh nisi. Donec sit amet tortor massa.
    • 20 |
    • 21 |
        22 |
      • Nested list the third.
      • 23 |
      • Nested list the third.
      • 24 |
      25 |
    • 26 |
    27 |
  • 28 |
  • Last item
  • 29 |
30 | 31 | 32 |

Ordered Lists

33 |
    34 |
  1. First item
  2. 35 |
  3. Second item 36 |
      37 |
    1. Nested unordered lists
    2. 38 |
    3. Nested line 2
    4. 39 |
    5. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed pulvinar sollicitudin tellus, vitae congue arcu condimentum ut. Fusce sagittis iaculis sem ut bibendum. Sed a ipsum purus, sit amet elementum diam. Fusce sapien sapien, cursus sit amet auctor sit amet, ultricies et lorem. Pellentesque dignissim gravida nunc, id porta felis dapibus nec. Proin ultrices, dui vel tempor imperdiet, mi libero congue orci, at auctor ante quam et lorem. Mauris imperdiet molestie leo at pellentesque. Maecenas nec nulla sed felis tristique sagittis. Suspendisse sit amet lectus vitae felis ultrices interdum vel nec ipsum. Proin elementum lectus sed nisi varius id euismod mauris lobortis. Phasellus eget purus ut nunc vulputate tincidunt sed ac purus. Aenean iaculis eros sed odio faucibus commodo. Morbi eu metus risus, ut convallis urna. Cras aliquet urna non diam rhoncus posuere. Aliquam est risus, ullamcorper ac malesuada eget, molestie vitae mauris. Nam volutpat, eros at scelerisque gravida, neque lorem interdum sapien, sed sollicitudin odio neque condimentum lacus. Curabitur congue tortor quis quam mattis lobortis. Quisque a ligula ipsum, eget hendrerit elit. Ut nec nibh nisi. Donec sit amet tortor massa.
    6. 40 |
    7. 41 |
        42 |
      1. Nested list the third.
      2. 43 |
      3. Nested list the third.
      4. 44 |
      45 |
    8. 46 |
    47 |
  4. 48 |
  5. Last item
  6. 49 |
50 | 51 |

Mixed Lists

52 |
    53 |
  • First item
  • 54 |
  • Second item 55 |
      56 |
    1. Nested unordered lists
    2. 57 |
    3. Nested line 2
    4. 58 |
    5. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed pulvinar sollicitudin tellus, vitae congue arcu condimentum ut. Fusce sagittis iaculis sem ut bibendum. Sed a ipsum purus, sit amet elementum diam. Fusce sapien sapien, cursus sit amet auctor sit amet, ultricies et lorem. Pellentesque dignissim gravida nunc, id porta felis dapibus nec. Proin ultrices, dui vel tempor imperdiet, mi libero congue orci, at auctor ante quam et lorem. Mauris imperdiet molestie leo at pellentesque. Maecenas nec nulla sed felis tristique sagittis. Suspendisse sit amet lectus vitae felis ultrices interdum vel nec ipsum. Proin elementum lectus sed nisi varius id euismod mauris lobortis. Phasellus eget purus ut nunc vulputate tincidunt sed ac purus. Aenean iaculis eros sed odio faucibus commodo. Morbi eu metus risus, ut convallis urna. Cras aliquet urna non diam rhoncus posuere. Aliquam est risus, ullamcorper ac malesuada eget, molestie vitae mauris. Nam volutpat, eros at scelerisque gravida, neque lorem interdum sapien, sed sollicitudin odio neque condimentum lacus. Curabitur congue tortor quis quam mattis lobortis. Quisque a ligula ipsum, eget hendrerit elit. Ut nec nibh nisi. Donec sit amet tortor massa.
    6. 59 |
    7. 60 |
        61 |
      • Nested list the third.
      • 62 |
      • Nested list the third.
      • 63 |
      64 |
    8. 65 |
    66 |
  • 67 |
  • Last item
  • 68 |
69 | 70 |
    71 |
  1. First item
  2. 72 |
  3. Second item 73 |
      74 |
    • Nested unordered lists
    • 75 |
    • Nested line 2
    • 76 |
    • Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed pulvinar sollicitudin tellus, vitae congue arcu condimentum ut. Fusce sagittis iaculis sem ut bibendum. Sed a ipsum purus, sit amet elementum diam. Fusce sapien sapien, cursus sit amet auctor sit amet, ultricies et lorem. Pellentesque dignissim gravida nunc, id porta felis dapibus nec. Proin ultrices, dui vel tempor imperdiet, mi libero congue orci, at auctor ante quam et lorem. Mauris imperdiet molestie leo at pellentesque. Maecenas nec nulla sed felis tristique sagittis. Suspendisse sit amet lectus vitae felis ultrices interdum vel nec ipsum. Proin elementum lectus sed nisi varius id euismod mauris lobortis. Phasellus eget purus ut nunc vulputate tincidunt sed ac purus. Aenean iaculis eros sed odio faucibus commodo. Morbi eu metus risus, ut convallis urna. Cras aliquet urna non diam rhoncus posuere. Aliquam est risus, ullamcorper ac malesuada eget, molestie vitae mauris. Nam volutpat, eros at scelerisque gravida, neque lorem interdum sapien, sed sollicitudin odio neque condimentum lacus. Curabitur congue tortor quis quam mattis lobortis. Quisque a ligula ipsum, eget hendrerit elit. Ut nec nibh nisi. Donec sit amet tortor massa.
    • 77 |
    • 78 |
        79 |
      1. Nested list the third.
      2. 80 |
      3. Nested list the third.
      4. 81 |
      82 |
    • 83 |
    84 |
  4. 85 |
  5. Last item
  6. 86 |
-------------------------------------------------------------------------------- /Core/Source/DTCoreTextParagraphStyle.h: -------------------------------------------------------------------------------- 1 | // 2 | // DTCoreTextParagraphStyle.h 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 4/14/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | /** 10 | `DTCoreTextParagraphStyle` encapsulates the paragraph or ruler attributes used by the NSAttributedString classes on iOS. It is a replacement for `NSParagraphStyle` which is not implemented on iOS. 11 | 12 | Since `NSAttributedString` instances use CTParagraphStyle object there are methods to bridge from and to these. Because of this distinction there is no need for a mutable variant of this class. 13 | */ 14 | @interface DTCoreTextParagraphStyle : NSObject 15 | 16 | /** 17 | @name Creating a DTCoreTextParagraphStyle 18 | */ 19 | 20 | /** 21 | Returns the default paragraph style. 22 | */ 23 | + (DTCoreTextParagraphStyle *)defaultParagraphStyle; 24 | 25 | 26 | 27 | /** 28 | @name Bridging to and from CTParagraphStyle 29 | */ 30 | 31 | /** 32 | Create a new paragraph style instance from a `CTParagraphStyle`. 33 | 34 | @param ctParagraphStyle the `CTParagraphStyle` from which to copy this new style's attributes. 35 | */ 36 | + (DTCoreTextParagraphStyle *)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)ctParagraphStyle; 37 | 38 | 39 | /** 40 | Create a new paragraph style instance from a `CTParagraphStyle`. 41 | 42 | @param ctParagraphStyle the `CTParagraphStyle` from which to copy this new style's attributes. 43 | */ 44 | - (id)initWithCTParagraphStyle:(CTParagraphStyleRef)ctParagraphStyle; 45 | 46 | /** 47 | Create a new `CTParagraphStyle` from the receiver for use as attribute in `NSAttributedString` 48 | 49 | @returns The `CTParagraphStyle` based on the receiver's attributes. 50 | */ 51 | - (CTParagraphStyleRef)createCTParagraphStyle; 52 | 53 | /** 54 | @name Bridging to and from NSParagraphStyle 55 | */ 56 | 57 | /** 58 | Create a new paragraph style instance from an `NSParagraphStyle`. 59 | 60 | Note: on iOS no tab stops are supported. 61 | @param paragraphStyle the `NSParagraphStyle` from which to copy this new style's attributes. 62 | */ 63 | + (DTCoreTextParagraphStyle *)paragraphStyleWithNSParagraphStyle:(NSParagraphStyle *)paragraphStyle; 64 | 65 | /** 66 | Create a new `NSParagraphStyle` from the receiver for use as attribute in `NSAttributedString`. 67 | 68 | Note: This method is requires iOS 6 or greater. This does not support tab stops. 69 | 70 | @returns The `NSParagraphStyle` based on the receiver's attributes. 71 | */ 72 | - (NSParagraphStyle *)NSParagraphStyle; 73 | 74 | 75 | /**------------------------------------------------------------------------------------- 76 | @name Accessing Style Information 77 | --------------------------------------------------------------------------------------- 78 | */ 79 | 80 | /** 81 | The indentation of the first line of the receiver. 82 | */ 83 | @property (nonatomic, assign) CGFloat firstLineHeadIndent; 84 | 85 | 86 | /** 87 | The document-wide default tab interval. 88 | 89 | The default tab interval in points. Tabs after the last specified in tabStops are placed at integer multiples of this distance (if positive). Default return value is 0.0. 90 | */ 91 | @property (nonatomic, assign) CGFloat defaultTabInterval; 92 | 93 | 94 | /** 95 | The distance between the paragraph’s top and the beginning of its text content. 96 | */ 97 | @property (nonatomic, assign) CGFloat paragraphSpacingBefore; 98 | 99 | 100 | /** 101 | The space after the end of the paragraph. 102 | */ 103 | @property (nonatomic, assign) CGFloat paragraphSpacing; 104 | 105 | 106 | /** 107 | The line height multiple. 108 | 109 | Internally line height multiples get converted into minimum and maximum line height. 110 | */ 111 | @property (nonatomic, assign) CGFloat lineHeightMultiple; 112 | 113 | 114 | /** 115 | The minimum height in points that any line in the receiver will occupy, regardless of the font size or size of any attached graphic. This value is always nonnegative. 116 | */ 117 | @property (nonatomic, assign) CGFloat minimumLineHeight; 118 | 119 | 120 | /** 121 | The maximum height in points that any line in the receiver will occupy, regardless of the font size or size of any attached graphic. This value is always nonnegative. The default value is 0. 122 | */ 123 | @property (nonatomic, assign) CGFloat maximumLineHeight; 124 | 125 | 126 | /** 127 | The distance in points from the leading margin of a text container to the beginning of lines other than the first. This value is always nonnegative. 128 | */ 129 | @property (nonatomic, assign) CGFloat headIndent; 130 | 131 | 132 | /** 133 | The distance in points from the right-sided margin of a text container. This value is always nonnegative. 134 | */ 135 | @property (nonatomic, assign) CGFloat tailIndent; 136 | 137 | 138 | /** 139 | The text alignment of the receiver. 140 | 141 | Natural text alignment is realized as left or right alignment depending on the line sweep direction of the first script contained in the paragraph. 142 | */ 143 | @property (nonatomic, assign) CTTextAlignment alignment; 144 | 145 | 146 | /** 147 | The base writing direction for the receiver. 148 | 149 | */ 150 | @property (nonatomic, assign) CTWritingDirection baseWritingDirection; 151 | 152 | 153 | /**------------------------------------------------------------------------------------- 154 | @name Setting Tab Stops 155 | --------------------------------------------------------------------------------------- 156 | */ 157 | 158 | /** 159 | The CTTextTab objects, sorted by location, that define the tab stops for the paragraph style. 160 | */ 161 | @property (nonatomic, copy) NSArray *tabStops; 162 | 163 | 164 | /** 165 | Adds a tab stop to the receiver. 166 | 167 | @param position the tab stop position 168 | @param alignment the tab alignment for this tab stop 169 | */ 170 | - (void)addTabStopAtPosition:(CGFloat)position alignment:(CTTextAlignment)alignment; 171 | 172 | 173 | /**------------------------------------------------------------------------------------- 174 | @name Interacting with CSS 175 | --------------------------------------------------------------------------------------- 176 | */ 177 | 178 | /** 179 | Create a representation suitable for CSS. 180 | 181 | @returns A string with the receiver's style encoded as CSS. 182 | */ 183 | - (NSString *)cssStyleRepresentation; 184 | 185 | 186 | /**------------------------------------------------------------------------------------- 187 | @name Setting Text Lists 188 | --------------------------------------------------------------------------------------- 189 | */ 190 | 191 | /** 192 | Text lists containing the paragraph, nested from outermost to innermost, to array. 193 | */ 194 | @property (nonatomic, copy) NSArray *textLists; 195 | 196 | 197 | /** 198 | The amount by which each list level is indented from the previous. NOTE: about to be replaced by textLists property. 199 | */ 200 | @property (nonatomic, assign) CGFloat listIndent; 201 | 202 | 203 | /**------------------------------------------------------------------------------------- 204 | @name Setting Text Blocks 205 | --------------------------------------------------------------------------------------- 206 | */ 207 | 208 | /** 209 | Text lists containing the paragraph, nested from outermost to innermost, to array. 210 | */ 211 | @property (nonatomic, copy) NSArray *textBlocks; 212 | 213 | 214 | @end 215 | -------------------------------------------------------------------------------- /Core/Source/DTCoreTextGlyphRun.m: -------------------------------------------------------------------------------- 1 | // 2 | // DTCoreTextGlyphRun.m 3 | // CoreTextExtensions 4 | // 5 | // Created by Oliver Drobnik on 1/25/11. 6 | // Copyright 2011 Drobnik.com. All rights reserved. 7 | // 8 | 9 | #import "DTCoreTextGlyphRun.h" 10 | #import "DTCoreTextLayoutLine.h" 11 | #import "DTTextAttachment.h" 12 | #import "DTCoreTextConstants.h" 13 | 14 | #ifndef __IPHONE_4_3 15 | #define __IPHONE_4_3 40300 16 | #endif 17 | 18 | #define SYNCHRONIZE_START(obj) dispatch_semaphore_wait(runLock, DISPATCH_TIME_FOREVER); 19 | #define SYNCHRONIZE_END(obj) dispatch_semaphore_signal(runLock); 20 | 21 | @interface DTCoreTextGlyphRun () 22 | @property (nonatomic, assign) CGRect frame; 23 | @property (nonatomic, assign) NSInteger numberOfGlyphs; 24 | @property (nonatomic, unsafe_unretained, readwrite) NSDictionary *attributes; 25 | @property (nonatomic, assign) dispatch_semaphore_t runLock; 26 | 27 | @end 28 | 29 | 30 | @implementation DTCoreTextGlyphRun 31 | { 32 | CTRunRef _run; 33 | 34 | CGRect _frame; 35 | 36 | CGFloat _offset; // x distance from line origin 37 | CGFloat _ascent; 38 | CGFloat _descent; 39 | CGFloat _leading; 40 | CGFloat _width; 41 | 42 | NSInteger _numberOfGlyphs; 43 | 44 | const CGPoint *_glyphPositionPoints; 45 | //BOOL needToFreeGlyphPositionPoints; 46 | 47 | __unsafe_unretained DTCoreTextLayoutLine *_line; // retain cycle, since these objects are retained by the _line 48 | __unsafe_unretained NSDictionary *_attributes; 49 | NSArray *_stringIndices; 50 | 51 | DTTextAttachment *_attachment; 52 | BOOL _hyperlink; 53 | 54 | BOOL _didCheckForAttachmentInAttributes; 55 | BOOL _didCheckForHyperlinkInAttributes; 56 | BOOL _didCalculateMetrics; 57 | } 58 | 59 | @synthesize runLock; 60 | 61 | - (id)initWithRun:(CTRunRef)run layoutLine:(DTCoreTextLayoutLine *)layoutLine offset:(CGFloat)offset 62 | { 63 | self = [super init]; 64 | 65 | if (self) 66 | { 67 | _run = run; 68 | CFRetain(_run); 69 | 70 | _offset = offset; 71 | _line = layoutLine; 72 | runLock = dispatch_semaphore_create(1); 73 | } 74 | 75 | return self; 76 | } 77 | 78 | - (void)dealloc 79 | { 80 | if (_run) 81 | { 82 | CFRelease(_run); 83 | } 84 | 85 | dispatch_release(runLock); 86 | } 87 | 88 | - (NSString *)description 89 | { 90 | return [NSString stringWithFormat:@"<%@ glyphs=%d %@>", [self class], [self numberOfGlyphs], NSStringFromCGRect(_frame)]; 91 | } 92 | 93 | #pragma mark Calculations 94 | - (void)calculateMetrics 95 | { 96 | // calculate metrics 97 | SYNCHRONIZE_START(self) 98 | { 99 | if (!_didCalculateMetrics) 100 | { 101 | _width = (CGFloat)CTRunGetTypographicBounds((CTRunRef)_run, CFRangeMake(0, 0), &_ascent, &_descent, &_leading); 102 | _didCalculateMetrics = YES; 103 | } 104 | } 105 | SYNCHRONIZE_END(self) 106 | } 107 | 108 | - (CGRect)frameOfGlyphAtIndex:(NSInteger)index 109 | { 110 | if (!_didCalculateMetrics) { 111 | [self calculateMetrics]; 112 | } 113 | if (!_glyphPositionPoints) 114 | { 115 | // this is a pointer to the points inside the run, thus no retain necessary 116 | _glyphPositionPoints = CTRunGetPositionsPtr(_run); 117 | } 118 | 119 | if (!_glyphPositionPoints || index >= self.numberOfGlyphs) 120 | { 121 | return CGRectNull; 122 | } 123 | 124 | CGPoint glyphPosition = _glyphPositionPoints[index]; 125 | 126 | CGRect rect = CGRectMake(_line.baselineOrigin.x + glyphPosition.x, _line.baselineOrigin.y - _ascent, _offset + _width - glyphPosition.x, _ascent + _descent); 127 | if (index < self.numberOfGlyphs-1) 128 | { 129 | rect.size.width = _glyphPositionPoints[index+1].x - glyphPosition.x; 130 | } 131 | 132 | return rect; 133 | } 134 | 135 | // TODO: fix indices if the stringRange is modified 136 | - (NSArray *)stringIndices 137 | { 138 | if (!_stringIndices) 139 | { 140 | const CFIndex *indices = CTRunGetStringIndicesPtr(_run); 141 | NSInteger count = self.numberOfGlyphs; 142 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:count]; 143 | NSInteger i; 144 | for (i = 0; i < count; i++) 145 | { 146 | [array addObject:[NSNumber numberWithInteger:indices[i]]]; 147 | } 148 | _stringIndices = array; 149 | } 150 | return _stringIndices; 151 | } 152 | 153 | // bounds of an image encompassing the entire run 154 | - (CGRect)imageBoundsInContext:(CGContextRef)context 155 | { 156 | return CTRunGetImageBounds(_run, context, CFRangeMake(0, 0)); 157 | } 158 | 159 | // range of the characters from the original string 160 | - (NSRange)stringRange 161 | { 162 | if (!_stringRange.length) 163 | { 164 | CFRange range = CTRunGetStringRange(_run); 165 | 166 | _stringRange = NSMakeRange(range.location, range.length); 167 | } 168 | 169 | return _stringRange; 170 | } 171 | 172 | - (void)drawInContext:(CGContextRef)context 173 | { 174 | if (!_run || !context) 175 | { 176 | return; 177 | } 178 | 179 | CGAffineTransform textMatrix = CTRunGetTextMatrix(_run); 180 | 181 | if (CGAffineTransformIsIdentity(textMatrix)) 182 | { 183 | CTRunDraw(_run, context, CFRangeMake(0, 0)); 184 | } 185 | else 186 | { 187 | CGPoint pos = CGContextGetTextPosition(context); 188 | 189 | // set tx and ty to current text pos according to docs 190 | textMatrix.tx = pos.x; 191 | textMatrix.ty = pos.y; 192 | 193 | CGContextSetTextMatrix(context, textMatrix); 194 | 195 | CTRunDraw(_run, context, CFRangeMake(0, 0)); 196 | 197 | // restore identity 198 | CGContextSetTextMatrix(context, CGAffineTransformIdentity); 199 | } 200 | } 201 | 202 | - (void)fixMetricsFromAttachment 203 | { 204 | if (self.attachment) 205 | { 206 | if (!_didCalculateMetrics) 207 | { 208 | [self calculateMetrics]; 209 | } 210 | 211 | _descent = 0; 212 | _ascent = self.attachment.displaySize.height; 213 | } 214 | } 215 | 216 | #pragma mark Properites 217 | - (NSInteger)numberOfGlyphs 218 | { 219 | if (!_numberOfGlyphs) 220 | { 221 | _numberOfGlyphs = CTRunGetGlyphCount(_run); 222 | } 223 | 224 | return _numberOfGlyphs; 225 | } 226 | 227 | - (NSDictionary *)attributes 228 | { 229 | if (!_attributes) 230 | { 231 | _attributes = (__bridge NSDictionary *)CTRunGetAttributes(_run); 232 | } 233 | 234 | return _attributes; 235 | } 236 | 237 | - (DTTextAttachment *)attachment 238 | { 239 | if (!_attachment) 240 | { 241 | if (!_didCheckForAttachmentInAttributes) 242 | { 243 | _attachment = [self.attributes objectForKey:NSAttachmentAttributeName]; 244 | 245 | _didCheckForAttachmentInAttributes = YES; 246 | } 247 | } 248 | 249 | return _attachment; 250 | } 251 | 252 | - (BOOL)isHyperlink 253 | { 254 | if (!_hyperlink) 255 | { 256 | if (!_didCheckForHyperlinkInAttributes) 257 | { 258 | _hyperlink = [self.attributes objectForKey:DTLinkAttribute]!=nil; 259 | 260 | _didCheckForHyperlinkInAttributes = YES; 261 | } 262 | } 263 | 264 | return _hyperlink; 265 | } 266 | 267 | - (CGRect)frame 268 | { 269 | if (!_didCalculateMetrics) 270 | { 271 | [self calculateMetrics]; 272 | } 273 | 274 | return CGRectMake(_line.baselineOrigin.x + _offset, _line.baselineOrigin.y - _ascent, _width, _ascent + _descent); 275 | } 276 | 277 | - (CGFloat)width 278 | { 279 | if (!_didCalculateMetrics) 280 | { 281 | [self calculateMetrics]; 282 | } 283 | 284 | return _width; 285 | } 286 | 287 | - (CGFloat)ascent 288 | { 289 | if (!_didCalculateMetrics) 290 | { 291 | [self calculateMetrics]; 292 | } 293 | 294 | return _ascent; 295 | } 296 | 297 | - (CGFloat)descent 298 | { 299 | if (!_didCalculateMetrics) 300 | { 301 | [self calculateMetrics]; 302 | } 303 | 304 | return _descent; 305 | } 306 | 307 | - (CGFloat)leading 308 | { 309 | if (!_didCalculateMetrics) 310 | { 311 | [self calculateMetrics]; 312 | } 313 | 314 | return _leading; 315 | } 316 | 317 | @synthesize frame = _frame; 318 | @synthesize numberOfGlyphs = _numberOfGlyphs; 319 | @synthesize attributes = _attributes; 320 | 321 | @synthesize ascent = _ascent; 322 | @synthesize descent = _descent; 323 | @synthesize leading = _leading; 324 | @synthesize attachment = _attachment; 325 | 326 | @end 327 | --------------------------------------------------------------------------------