Epub阅读器0.0.1
This commit is contained in:
@@ -0,0 +1,643 @@
|
||||
//
|
||||
// DTHTMLAttributedStringBuilder.m
|
||||
// DTCoreText (WeRead custom fork)
|
||||
//
|
||||
// Reverse-engineered pseudo-implementation from WeChat Reading binary.
|
||||
// This file reconstructs the full HTML → NSAttributedString pipeline.
|
||||
//
|
||||
// Pipeline overview:
|
||||
// 1. Parse raw HTML via DTHTMLParser (SAX-style).
|
||||
// 2. Each SAX event (startElement / endElement / foundCharacters / CDATA)
|
||||
// builds or mutates DTHTMLElement nodes in a DOM tree.
|
||||
// 3. After parsing completes, -_buildString walks the DOM tree and calls
|
||||
// -[DTHTMLElement attributedString] recursively.
|
||||
// 4. The resulting NSAttributedString is stored in _generatedAttributedString.
|
||||
//
|
||||
|
||||
#import "DTHTMLAttributedStringBuilder.h"
|
||||
#import "DTHTMLParserDelegate.h"
|
||||
#import "DTHTMLElement.h"
|
||||
#import "DTCSSStylesheet.h"
|
||||
#import "DTCoreTextFontDescriptor.h"
|
||||
#import "DTCoreTextParagraphStyle.h"
|
||||
#import "DTHTMLParser.h"
|
||||
#import "WRBook.h"
|
||||
#import "WRChapter.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WeRead custom attribute key definitions
|
||||
// ---------------------------------------------------------------------------
|
||||
NSString *const DTPageBackgroundColorAttribute = @"DTPageBackgroundColorAttribute";
|
||||
NSString *const DTPageBackgroundImageAttribute = @"DTPageBackgroundImageAttribute";
|
||||
NSString *const DTPageBackgroundImagePathAttribute = @"DTPageBackgroundImagePathAttribute";
|
||||
NSString *const DTPageBreakAfterAttribute = @"DTPageBreakAfterAttribute";
|
||||
NSString *const DTPageBreakBeforeAttribute = @"DTPageBreakBeforeAttribute";
|
||||
NSString *const DTPageBreakInsideAvoidAttribute = @"DTPageBreakInsideAvoidAttribute";
|
||||
NSString *const DTPageRelateAttribute = @"DTPageRelateAttribute";
|
||||
NSString *const DTPageSize = @"DTPageSize";
|
||||
NSString *const DTPageFlippingStyle = @"DTPageFlippingStyle";
|
||||
NSString *const DTHTMLVerticalCenterAttribute = @"DTHTMLVerticalCenterAttribute";
|
||||
NSString *const DTHTMLTranslateTagAttribute = @"DTHTMLTranslateTagAttribute";
|
||||
NSString *const DTHTMLTranslateNoStyleAttribute = @"DTHTMLTranslateNoStyleAttribute";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
#pragma mark - Private interface
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@interface DTHTMLAttributedStringBuilder ()
|
||||
{
|
||||
// ---- ivar: the SAX parser delegate that accumulates the DOM tree ----
|
||||
DTHTMLParserDelegate *_parserDelegate;
|
||||
|
||||
// ---- Cached input ----
|
||||
NSData *_htmlData;
|
||||
DTCSSStylesheet *_cssStyleSheet;
|
||||
NSDictionary *_options;
|
||||
|
||||
// ---- Result ----
|
||||
NSAttributedString *_generatedAttributedString;
|
||||
|
||||
// ---- WeRead book/chapter context ----
|
||||
WRBook *_book;
|
||||
WRChapter *_chapter;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
#pragma mark - DTHTMLParserDelegate (internal helper)
|
||||
// ---------------------------------------------------------------------------
|
||||
// In the actual binary this is a separate class whose ivars include all the
|
||||
// mutable state needed during parsing. We define it here to show the fields
|
||||
// that DTHTMLAttributedStringBuilder delegates to.
|
||||
|
||||
@interface DTHTMLParserDelegate : NSObject
|
||||
{
|
||||
// Current tag handlers (block-based dispatch tables keyed on tag name)
|
||||
NSData *_tagStartHandlers; // actually a block map
|
||||
NSDictionary *_tagEndHandlers;
|
||||
|
||||
// CSS stylesheet applied during parsing
|
||||
DTCSSStylesheet *_cssStyleSheet;
|
||||
|
||||
// Base URL for resolving relative links / images
|
||||
NSURL *_baseURL;
|
||||
|
||||
// Default font / paragraph descriptors used when no CSS overrides exist
|
||||
DTCoreTextFontDescriptor *_defaultFontDescriptor;
|
||||
DTCoreTextParagraphStyle *_defaultParagraphStyle;
|
||||
|
||||
// The root of the DOM tree being built
|
||||
DTHTMLElement *_rootElement;
|
||||
|
||||
// Stack of open elements (for nesting / parent resolution)
|
||||
NSMutableDictionary *_elementStack; // index → DTHTMLElement
|
||||
|
||||
// Accumulated text for the current text run
|
||||
NSMutableDictionary *_currentTextBuffer;
|
||||
|
||||
// "Current" element pointers (set during SAX traversal)
|
||||
DTHTMLElement *_currentElement;
|
||||
DTHTMLElement *_parentElement;
|
||||
DTHTMLElement *_lastInlineElement;
|
||||
DTHTMLElement *_lastBlockElement;
|
||||
|
||||
// WeRead-specific rendering context
|
||||
WRBook *_book;
|
||||
WRChapter *_chapter;
|
||||
|
||||
// Final output accumulator
|
||||
NSMutableAttributedString *_outputString;
|
||||
|
||||
// Image / view references (for lazy image loading)
|
||||
UIImageView *_currentImageView;
|
||||
UIView *_imageContainerView;
|
||||
UIView *_currentView;
|
||||
NSString *_currentImageSrc;
|
||||
UIView *_parentView;
|
||||
UIView *_rootView;
|
||||
|
||||
// Collected tag list for post-processing
|
||||
NSArray *_tagOrder;
|
||||
}
|
||||
|
||||
// Methods used during SAX parsing
|
||||
- (void)_registerTagStartHandlers;
|
||||
- (void)_registerTagEndHandlers;
|
||||
|
||||
@end
|
||||
|
||||
@implementation DTHTMLParserDelegate
|
||||
|
||||
// --------------------------------------------------
|
||||
# pragma mark Handler registration
|
||||
// --------------------------------------------------
|
||||
|
||||
/**
|
||||
Registers block handlers for HTML start tags.
|
||||
Each handler receives the element name, attributes dict, and position,
|
||||
then creates or configures the appropriate DTHTMLElement subclass.
|
||||
|
||||
WeRead registers custom handlers for their proprietary CSS attributes:
|
||||
- wr-vertical-center-style → DTHTMLVerticalCenterAttribute
|
||||
- weread-page-relate → DTPageRelateAttribute
|
||||
- avoidPageBreakInside → DTPageBreakInsideAvoidAttribute
|
||||
- DTPageBreakAfter / DTPageBreakBefore
|
||||
- DTPageBackgroundColor / DTPageBackgroundImage
|
||||
*/
|
||||
- (void)_registerTagStartHandlers
|
||||
{
|
||||
// Pseudo-code: build a dictionary mapping tag names to handler blocks.
|
||||
//
|
||||
// _tagStartHandlers = @{
|
||||
// @"p" : ^(NSString *tag, NSDictionary *attrs, NSUInteger pos) { ... },
|
||||
// @"div" : ^(NSString *tag, NSDictionary *attrs, NSUInteger pos) { ... },
|
||||
// @"img" : ^(NSString *tag, NSDictionary *attrs, NSUInteger pos) { ... },
|
||||
// @"br" : ^(NSString *tag, NSDictionary *attrs, NSUInteger pos) { ... },
|
||||
// @"a" : ^(NSString *tag, NSDictionary *attrs, NSUInteger pos) { ... },
|
||||
// @"h1" : ^(NSString *tag, NSDictionary *attrs, NSUInteger pos) { ... },
|
||||
// ...
|
||||
// // WeRead custom tags / attributes handled here
|
||||
// };
|
||||
//
|
||||
// Each handler typically:
|
||||
// 1. Creates a new DTHTMLElement.
|
||||
// 2. Sets its tagName, attributes, parent.
|
||||
// 3. Calls -[DTHTMLElement applyStyleDictionary:isLatinLanguageBook:].
|
||||
// 4. Pushes the element onto _elementStack.
|
||||
// 5. Updates _currentElement.
|
||||
}
|
||||
|
||||
/**
|
||||
Registers block handlers for HTML end tags.
|
||||
Responsible for:
|
||||
- Popping the element from the stack.
|
||||
- Finalizing inline text runs.
|
||||
- Applying paragraph-level styles.
|
||||
- Handling DTPageBreakAfter / Before attributes.
|
||||
*/
|
||||
- (void)_registerTagEndHandlers
|
||||
{
|
||||
// Pseudo-code: analogous to start handlers but for closing tags.
|
||||
//
|
||||
// _tagEndHandlers = @{
|
||||
// @"p" : ^(NSString *tag) { ... },
|
||||
// @"div" : ^(NSString *tag) { ... },
|
||||
// ...
|
||||
// };
|
||||
//
|
||||
// Each handler typically:
|
||||
// 1. Pops the current element from the stack.
|
||||
// 2. Calls -[DTHTMLElement interpretAttributes] to finalize.
|
||||
// 3. If element has DTPageBreakAfter, inserts page break marker.
|
||||
// 4. Sets _currentElement back to the parent.
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
#pragma mark - DTHTMLAttributedStringBuilder implementation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@implementation DTHTMLAttributedStringBuilder
|
||||
|
||||
// --------------------------------------------------
|
||||
# pragma mark Initialization
|
||||
// --------------------------------------------------
|
||||
|
||||
- (instancetype)initWithHTML:(NSData *)htmlData
|
||||
options:(NSDictionary *)options
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_htmlData = [htmlData copy];
|
||||
_options = [options copy] ?: @{};
|
||||
|
||||
// Create the parser delegate and configure it.
|
||||
_parserDelegate = [[DTHTMLParserDelegate alloc] init];
|
||||
|
||||
// If a CSS stylesheet is provided in options, install it.
|
||||
DTCSSStylesheet *sheet = _options[@"DTDefaultCSSStyleSheet"];
|
||||
if (sheet) {
|
||||
_cssStyleSheet = sheet;
|
||||
_parserDelegate->_cssStyleSheet = sheet;
|
||||
}
|
||||
|
||||
// Base URL for resolving <img src>, <a href>, etc.
|
||||
NSURL *baseURL = _options[@"DTBaseURL"];
|
||||
if (baseURL) {
|
||||
_parserDelegate->_baseURL = baseURL;
|
||||
}
|
||||
|
||||
// Default font descriptor (system font fallback).
|
||||
DTCoreTextFontDescriptor *fontDesc = _options[@"DTDefaultFontDescriptor"];
|
||||
if (fontDesc) {
|
||||
_parserDelegate->_defaultFontDescriptor = fontDesc;
|
||||
}
|
||||
|
||||
// Default paragraph style.
|
||||
DTCoreTextParagraphStyle *paraStyle = _options[@"DTDefaultParagraphStyle"];
|
||||
if (paraStyle) {
|
||||
_parserDelegate->_defaultParagraphStyle = paraStyle;
|
||||
}
|
||||
|
||||
// Register the tag handler dispatch tables.
|
||||
[_parserDelegate _registerTagStartHandlers];
|
||||
[_parserDelegate _registerTagEndHandlers];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithHTML:(NSData *)htmlData
|
||||
cssStyleSheet:(DTCSSStylesheet *)styleSheet
|
||||
options:(NSDictionary *)options
|
||||
{
|
||||
// Merge stylesheet into options so the designated init picks it up.
|
||||
NSMutableDictionary *merged = [options mutableCopy] ?: [NSMutableDictionary dictionary];
|
||||
if (styleSheet) {
|
||||
merged[@"DTDefaultCSSStyleSheet"] = styleSheet;
|
||||
}
|
||||
return [self initWithHTML:htmlData options:merged];
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
# pragma mark Build pipeline
|
||||
// --------------------------------------------------
|
||||
|
||||
/**
|
||||
Main entry point. Kicks off the SAX parser; when parsing completes the
|
||||
delegate has built a DOM tree of DTHTMLElement nodes. Then -_buildString
|
||||
converts that tree into the final NSAttributedString.
|
||||
*/
|
||||
- (void)buildString
|
||||
{
|
||||
// Step 1: Create a SAX parser and point it at our delegate.
|
||||
DTHTMLParser *parser = [[DTHTMLParser alloc] initWithData:_htmlData];
|
||||
parser.delegate = _parserDelegate;
|
||||
|
||||
// Step 2: Parse. This triggers the delegate callbacks below.
|
||||
[parser parse];
|
||||
|
||||
// Step 3: Convert the DOM tree to an attributed string.
|
||||
[self _buildString];
|
||||
}
|
||||
|
||||
/**
|
||||
Walks the DOM tree rooted at _parserDelegate->_rootElement and recursively
|
||||
calls -[DTHTMLElement attributedString] to produce the final output.
|
||||
*/
|
||||
- (void)_buildString
|
||||
{
|
||||
DTHTMLElement *root = _parserDelegate->_rootElement;
|
||||
if (!root) {
|
||||
_generatedAttributedString = [[NSAttributedString alloc] initWithString:@""];
|
||||
return;
|
||||
}
|
||||
|
||||
// Recursively convert the DOM tree.
|
||||
// DTHTMLElement's -attributedString walks children and concatenates.
|
||||
NSAttributedString *result = [root attributedString];
|
||||
|
||||
// If the result is nil (empty document), produce an empty string.
|
||||
if (!result) {
|
||||
result = [[NSAttributedString alloc] initWithString:@""];
|
||||
}
|
||||
|
||||
_generatedAttributedString = result;
|
||||
|
||||
// Store into the delegate's output for external access if needed.
|
||||
_parserDelegate->_outputString = [result mutableCopy];
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
# pragma mark DTHTMLParser delegate callbacks
|
||||
// --------------------------------------------------
|
||||
|
||||
/**
|
||||
Called by the SAX parser when an opening HTML tag is encountered.
|
||||
|
||||
@param parser The DTHTMLParser instance.
|
||||
@param elementName Tag name (e.g. "p", "div", "img").
|
||||
@param attributeDict Parsed attributes from the HTML tag.
|
||||
@param position Character offset in the original HTML data.
|
||||
*/
|
||||
- (void)parser:(id)parser
|
||||
didStartElement:(NSString *)elementName
|
||||
attributes:(NSDictionary *)attributeDict
|
||||
position:(NSUInteger)position
|
||||
{
|
||||
// Look up the registered handler block for this tag.
|
||||
// If found, invoke it. Otherwise, fall through to default handling.
|
||||
|
||||
DTHTMLElement *newElement = [[DTHTMLElement alloc] initWithTagName:elementName
|
||||
attributes:attributeDict];
|
||||
|
||||
// Set the parent to the current element on the stack.
|
||||
DTHTMLElement *parent = _parserDelegate->_currentElement;
|
||||
newElement.parent = parent;
|
||||
[parent.children addObject:newElement];
|
||||
|
||||
// Push onto the element stack.
|
||||
_parserDelegate->_currentElement = newElement;
|
||||
|
||||
// Apply inline style attribute (style="...") and any CSS rules
|
||||
// matching this element.
|
||||
NSDictionary *styleDict = [self _resolveStyleForElement:newElement
|
||||
attributes:attributeDict];
|
||||
if (styleDict) {
|
||||
// Determine if this is a Latin-language book (affects font fallback).
|
||||
BOOL isLatin = [_parserDelegate->_book isLatinLanguageBook];
|
||||
[newElement applyStyleDictionary:styleDict isLatinLanguageBook:isLatin];
|
||||
}
|
||||
|
||||
// Handle WeRead-specific custom CSS attributes.
|
||||
[self _applyWeReadCustomAttributes:newElement fromAttributes:attributeDict];
|
||||
|
||||
// Handle special tags.
|
||||
if ([elementName caseInsensitiveCompare:@"img"] == NSOrderedSame) {
|
||||
[self _handleImageElement:newElement attributes:attributeDict];
|
||||
}
|
||||
else if ([elementName caseInsensitiveCompare:@"br"] == NSOrderedSame) {
|
||||
[self _handleBRElement:newElement];
|
||||
}
|
||||
else if ([elementName caseInsensitiveCompare:@"a"] == NSOrderedSame) {
|
||||
[self _handleAnchorElement:newElement attributes:attributeDict];
|
||||
}
|
||||
|
||||
// Track last block vs. inline element for layout decisions.
|
||||
if ([newElement isBlockElement]) {
|
||||
_parserDelegate->_lastBlockElement = newElement;
|
||||
} else {
|
||||
_parserDelegate->_lastInlineElement = newElement;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Called when character data is found between tags.
|
||||
|
||||
@param parser The DTHTMLParser instance.
|
||||
@param string The character data.
|
||||
@param position Character offset in the original HTML.
|
||||
*/
|
||||
- (void)parser:(id)parser
|
||||
foundCharacters:(NSString *)string
|
||||
position:(NSUInteger)position
|
||||
{
|
||||
if (!string || string.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Append to the current text buffer.
|
||||
// The delegate accumulates text until a closing tag flushes it.
|
||||
DTHTMLElement *current = _parserDelegate->_currentElement;
|
||||
if (current) {
|
||||
[current appendText:string];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Called when a CDATA section is encountered (e.g. inside <script> or <style>).
|
||||
WeRead uses CDATA in some book content.
|
||||
|
||||
@param parser The DTHTMLParser instance.
|
||||
@param CDATABlock Raw CDATA bytes.
|
||||
*/
|
||||
- (void)parser:(id)parser
|
||||
foundCDATA:(NSData *)CDATABlock
|
||||
{
|
||||
// CDATA is typically treated as raw text content.
|
||||
NSString *text = [[NSString alloc] initWithData:CDATABlock
|
||||
encoding:NSUTF8StringEncoding];
|
||||
if (text) {
|
||||
DTHTMLElement *current = _parserDelegate->_currentElement;
|
||||
if (current) {
|
||||
[current appendText:text];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Called when the parser finishes parsing the entire document.
|
||||
*/
|
||||
- (void)parserDidEndDocument:(id)parser
|
||||
{
|
||||
// All elements have been opened and closed.
|
||||
// The DOM tree is complete in _parserDelegate->_rootElement.
|
||||
// Post-processing can happen here if needed.
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
# pragma mark Style resolution
|
||||
// --------------------------------------------------
|
||||
|
||||
/**
|
||||
Resolves the effective style dictionary for an element by merging:
|
||||
1. CSS stylesheet rules matching this element.
|
||||
2. Inline style="" attribute.
|
||||
3. Element-specific default styles.
|
||||
*/
|
||||
- (NSDictionary *)_resolveStyleForElement:(DTHTMLElement *)element
|
||||
attributes:(NSDictionary *)attrs
|
||||
{
|
||||
NSMutableDictionary *resolved = [NSMutableDictionary dictionary];
|
||||
|
||||
// 1. Apply CSS stylesheet rules (class, id, tag selectors).
|
||||
if (_cssStyleSheet) {
|
||||
NSDictionary *cssRules = [_cssStyleSheet stylesForElement:element];
|
||||
if (cssRules) {
|
||||
[resolved addEntriesFromDictionary:cssRules];
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Parse inline style attribute.
|
||||
NSString *inlineStyle = attrs[@"style"];
|
||||
if (inlineStyle) {
|
||||
NSDictionary *inlineDict = [self _parseInlineStyle:inlineStyle];
|
||||
if (inlineDict) {
|
||||
[resolved addEntriesFromDictionary:inlineDict];
|
||||
}
|
||||
}
|
||||
|
||||
return resolved.count > 0 ? resolved : nil;
|
||||
}
|
||||
|
||||
/**
|
||||
Parses a CSS inline style string (e.g. "color:red;font-size:14px")
|
||||
into a dictionary.
|
||||
*/
|
||||
- (NSDictionary *)_parseInlineStyle:(NSString *)styleString
|
||||
{
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
||||
NSArray *declarations = [styleString componentsSeparatedByString:@";"];
|
||||
for (NSString *decl in declarations) {
|
||||
NSArray *parts = [decl componentsSeparatedByString:@":"];
|
||||
if (parts.count == 2) {
|
||||
NSString *key = [parts[0] stringByTrimmingCharactersInSet:
|
||||
[NSCharacterSet whitespaceCharacterSet]];
|
||||
NSString *val = [parts[1] stringByTrimmingCharactersInSet:
|
||||
[NSCharacterSet whitespaceCharacterSet]];
|
||||
if (key.length > 0 && val.length > 0) {
|
||||
dict[key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
# pragma mark WeRead custom attribute handling
|
||||
// --------------------------------------------------
|
||||
|
||||
/**
|
||||
Applies WeRead-specific CSS attributes to the element.
|
||||
These control page layout features unique to WeRead's reading engine.
|
||||
*/
|
||||
- (void)_applyWeReadCustomAttributes:(DTHTMLElement *)element
|
||||
fromAttributes:(NSDictionary *)attrs
|
||||
{
|
||||
// wr-vertical-center-style: vertically center content within page
|
||||
NSString *vCenter = attrs[@"wr-vertical-center-style"];
|
||||
if (vCenter) {
|
||||
element.verticalCenterStyle = vCenter;
|
||||
}
|
||||
|
||||
// weread-page-relate: marks content as page-related
|
||||
NSString *pageRelate = attrs[@"weread-page-relate"];
|
||||
if (pageRelate) {
|
||||
element.pageRelate = pageRelate;
|
||||
}
|
||||
|
||||
// avoidPageBreakInside: prevent page breaks within this element
|
||||
NSString *avoidBreak = attrs[@"avoidPageBreakInside"];
|
||||
if ([avoidBreak boolValue] || [avoidBreak isEqualToString:@"true"]) {
|
||||
element.shouldAvoidPageBreakInside = YES;
|
||||
}
|
||||
|
||||
// DTPageBreakAfter: force page break after this element
|
||||
NSString *breakAfter = attrs[@"DTPageBreakAfter"];
|
||||
if (breakAfter) {
|
||||
element.pageBreakAfter = YES;
|
||||
}
|
||||
|
||||
// DTPageBreakBefore: force page break before this element
|
||||
NSString *breakBefore = attrs[@"DTPageBreakBefore"];
|
||||
if (breakBefore) {
|
||||
element.pageBreakBefore = YES;
|
||||
}
|
||||
|
||||
// DTPageBackgroundColor: per-page background color
|
||||
NSString *bgColor = attrs[@"DTPageBackgroundColor"];
|
||||
if (bgColor) {
|
||||
element.pageBackgroundColor = [self _colorFromCSSValue:bgColor];
|
||||
}
|
||||
|
||||
// DTPageBackgroundImage: per-page background image
|
||||
NSString *bgImage = attrs[@"DTPageBackgroundImage"];
|
||||
if (bgImage) {
|
||||
element.pageBackgroundImage = bgImage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Converts a CSS color value string to UIColor.
|
||||
*/
|
||||
- (UIColor *)_colorFromCSSValue:(NSString *)cssValue
|
||||
{
|
||||
// Simplified: real implementation handles hex (#RRGGBB), rgb(), named colors.
|
||||
if ([cssValue hasPrefix:@"#"]) {
|
||||
NSString *hex = [cssValue substringFromIndex:1];
|
||||
unsigned int rgb = 0;
|
||||
[[NSScanner scannerWithString:hex] scanHexInt:&rgb];
|
||||
return [UIColor colorWithRed:((rgb >> 16) & 0xFF) / 255.0
|
||||
green:((rgb >> 8) & 0xFF) / 255.0
|
||||
blue:((rgb >> 0) & 0xFF) / 255.0
|
||||
alpha:1.0];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
# pragma mark Special element handlers
|
||||
// --------------------------------------------------
|
||||
|
||||
- (void)_handleImageElement:(DTHTMLElement *)element
|
||||
attributes:(NSDictionary *)attrs
|
||||
{
|
||||
// Resolve image source relative to base URL.
|
||||
NSString *src = attrs[@"src"];
|
||||
if (!src) return;
|
||||
|
||||
NSURL *baseURL = _parserDelegate->_baseURL;
|
||||
NSURL *imageURL = [NSURL URLWithString:src relativeToURL:baseURL];
|
||||
|
||||
element.imageURL = imageURL;
|
||||
|
||||
// Store for lazy loading pipeline.
|
||||
_parserDelegate->_currentImageSrc = src;
|
||||
}
|
||||
|
||||
- (void)_handleBRElement:(DTHTMLElement *)element
|
||||
{
|
||||
// <br> inserts a newline / line break.
|
||||
element.tagName = @"br";
|
||||
element.isLineBreak = YES;
|
||||
}
|
||||
|
||||
- (void)_handleAnchorElement:(DTHTMLElement *)element
|
||||
attributes:(NSDictionary *)attrs
|
||||
{
|
||||
// <a href="..."> creates a hyperlink.
|
||||
NSString *href = attrs[@"href"];
|
||||
if (href) {
|
||||
NSURL *baseURL = _parserDelegate->_baseURL;
|
||||
element.linkURL = [NSURL URLWithString:href relativeToURL:baseURL];
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
# pragma mark Properties
|
||||
// --------------------------------------------------
|
||||
|
||||
- (NSData *)htmlData
|
||||
{
|
||||
return _htmlData;
|
||||
}
|
||||
|
||||
- (DTCSSStylesheet *)cssStyleSheet
|
||||
{
|
||||
return _cssStyleSheet;
|
||||
}
|
||||
|
||||
- (NSDictionary *)options
|
||||
{
|
||||
return _options;
|
||||
}
|
||||
|
||||
- (NSAttributedString *)generatedAttributedString
|
||||
{
|
||||
return _generatedAttributedString;
|
||||
}
|
||||
|
||||
- (WRBook *)book
|
||||
{
|
||||
return _parserDelegate->_book;
|
||||
}
|
||||
|
||||
- (void)setBook:(WRBook *)book
|
||||
{
|
||||
_parserDelegate->_book = book;
|
||||
_book = book;
|
||||
}
|
||||
|
||||
- (WRChapter *)chapter
|
||||
{
|
||||
return _parserDelegate->_chapter;
|
||||
}
|
||||
|
||||
- (void)setChapter:(WRChapter *)chapter
|
||||
{
|
||||
_parserDelegate->_chapter = chapter;
|
||||
_chapter = chapter;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user