DTCoreText在项目中已经有了成熟的应用,在iphone和ipad上都有着相关应用;在ipad也做到了图文混排,这里打算对DTCoreText项目做个简单的分析和归纳。
从整体的结构上来讲,DTCoreText的处理步骤为: 解析Html结构(使用libxml2库)、根据分析结果生成NSAttributedString、再通过NSAttributedString利用CoreText绘图,完成整个过程。
当然中间会涉及一系列的处理步骤和问题,我打算列出部分。
首先从解析Html结构开始,这里采用的是libxml2的SAX处理方式:
DTHTMLParser 类负责和libxml2库接口衔接,
void _startDocument(void *context); void _endDocument(void *context); void _startElement(void *context, const xmlChar *name,const xmlChar **atts); void _endElement(void *context, const xmlChar *name); void _characters(void *context, const xmlChar *ch, int len); void _comment(void *context, const xmlChar *value); void _dterror(void *context, const char *msg, ...); void _cdataBlock(void *context, const xmlChar *value, int len); void _ignorableWhitespace (void *context, const xmlChar *ch, int len);
// 注册回调的函数入口 if ([_delegate respondsToSelector:@selector(parserDidStartDocument:)]) { _handler.startDocument = _startDocument; } else { _handler.startDocument = NULL; } if ([_delegate respondsToSelector:@selector(parserDidEndDocument:)]) { _handler.endDocument = _endDocument; } else { _handler.endDocument = NULL; } if ([delegate respondsToSelector:@selector(parser:didStartElement:attributes:)]) { _handler.startElement = _startElement; } else { _handler.startElement = NULL; }
xmlSAX2InitHtmlDefaultSAXHandler(&_handler); // 注册回调接口 // create a parse context 开始调用解析 _parserContext = htmlCreatePushParserCtxt(&_handler, (__bridge void *)self, dataBytes, (int)dataSize, NULL, charEnc); // set some options htmlCtxtUseOptions(_parserContext, HTML_PARSE_RECOVER | HTML_PARSE_NONET | HTML_PARSE_COMPACT | HTML_PARSE_NOBLANKS);
DTHTMLAttributedStringBuilder类中负责处理对应的事件回调,这里使用了两个GCD队列来处理解析,_stringParsingQueue负责处理调用DTHTMLParser解析,
_stringAssemblyGroup负责处理对应标签回调事件中NSAttributedString处理,最后dispatch_group_wait等待两个队列都完成后,再返回结果。
解析具体步骤:
1. 处理前,通过_registerTagStartHandlers和_registerTagEndHandlers注册对应的标签block,这里可以定制想要处理的html块,如果对于部分不想处理的html标签,这里就可以忽略
2. 对于样式,通过DTCSSStylesheet来解析处理,其中方法 parseStyleBlock 用来解析css文本,这里使用了纯文本的解析方式,具体解析方法这里就不写了;其实个人感觉,html核心还是在CSS布局上,DTCoreText只提供对部分css的支持;后面再把css布局和CoreText联系起来。
3. 由于html是dom结构,DTHTMLAttributedStringBuilder采用了当前结构的方式,也就是保存当前的上下文 DTHTMLElement *currentTag;
DTHTMLElement结构:
@property (nonatomic, strong) DTHTMLElement *parent; @property (nonatomic, copy) DTCoreTextFontDescriptor *fontDescriptor; @property (nonatomic, copy) DTCoreTextParagraphStyle *paragraphStyle; @property (nonatomic, strong) DTTextAttachment *textAttachment; @property (nonatomic, copy) NSURL *link; @property (nonatomic, copy) NSString *anchorName; @property (nonatomic, strong) DTColor *textColor; @property (nonatomic, strong) DTColor *backgroundColor; @property (nonatomic, copy) NSString * backgroundImageName; @property (nonatomic, copy) NSString * answerInfo; @property (nonatomic, assign) BOOL isAnswEnd; @property (nonatomic, assign) BOOL isCntEnd; @property (nonatomic, copy) NSString *tagName; @property (nonatomic, copy) NSString *beforeContent; @property (nonatomic, copy) NSString *text; @property (nonatomic, copy) NSArray *shadows; @property (nonatomic, assign) CTUnderlineStyle underlineStyle; @property (nonatomic, assign) BOOL tagContentInvisible; @property (nonatomic, assign) BOOL strikeOut; @property (nonatomic, assign) NSInteger superscriptStyle; @property (nonatomic, assign) NSInteger headerLevel; @property (nonatomic, assign) DTHTMLElementDisplayStyle displayStyle; @property (nonatomic, readonly) DTHTMLElementFloatStyle floatStyle; @property (nonatomic, assign) BOOL isColorInherited; @property (nonatomic, assign) BOOL preserveNewlines; @property (nonatomic, assign) DTHTMLElementFontVariant fontVariant; @property (nonatomic, assign) CGFloat textScale; @property (nonatomic, assign) CGSize size; @property (nonatomic, strong) NSDictionary *attributes;
这里保存了父节点,和诸多样式;这里可以扩展设置相关属性,用于后面的Coretext使用;比如 isAnswEnd isCntEnd都是我们项目中自定义的属性
4. 接下来就是调用
- (void)parser:(DTHTMLParser *)parser didStartElement:(NSString *)elementName attributes:(NSDictionary *)attributeDict
这个方法处理节点和样式,并负责调用标签对应的块,这里主要是设置各种属性,然后通过 [tmpString appendAttributedString:[currentTag attributedString]];来构造attributedString,通过
[tmpString appendString:string]; 增加下纯文本
5. 接着就是调用
- (void)parser:(DTHTMLParser *)parser didEndElement:(NSString *)elementName DTHTMLElement *popChild = currentTag; currentTag = currentTag.parent; [currentTag removeChild:popChild];
包括一些退栈处理等
到此,NSAttributedString已经生成了,接下来就是要对NSAttributedString做相关的展示处理。