1/*
2 * Copyright (C) 2011, 2012 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "HTMLConverter.h"
28
29#import "ArchiveResource.h"
30#import "CSSComputedStyleDeclaration.h"
31#import "CSSParser.h"
32#import "CSSPrimitiveValue.h"
33#import "CachedImage.h"
34#import "CharacterData.h"
35#import "ColorMac.h"
36#import "Document.h"
37#import "DocumentLoader.h"
38#import "Element.h"
39#import "ElementTraversal.h"
40#import "Font.h"
41#import "Frame.h"
42#import "FrameLoader.h"
43#import "HTMLElement.h"
44#import "HTMLFrameElementBase.h"
45#import "HTMLInputElement.h"
46#import "HTMLMetaElement.h"
47#import "HTMLNames.h"
48#import "HTMLOListElement.h"
49#import "HTMLParserIdioms.h"
50#import "HTMLTableCellElement.h"
51#import "HTMLTextAreaElement.h"
52#import "LoaderNSURLExtras.h"
53#import "RGBColor.h"
54#import "RenderImage.h"
55#import "SoftLinking.h"
56#import "StyleProperties.h"
57#import "StyledElement.h"
58#import "TextIterator.h"
59#import <objc/runtime.h>
60#import <wtf/ASCIICType.h>
61#import <wtf/text/StringBuilder.h>
62
63#if PLATFORM(IOS)
64
65SOFT_LINK_FRAMEWORK(UIKit)
66SOFT_LINK_CLASS(UIKit, UIColor)
67
68SOFT_LINK_PRIVATE_FRAMEWORK(UIFoundation)
69SOFT_LINK_CLASS(UIFoundation, UIFont)
70SOFT_LINK_CLASS(UIFoundation, NSColor)
71SOFT_LINK_CLASS(UIFoundation, NSShadow)
72SOFT_LINK_CLASS(UIFoundation, NSTextAttachment)
73SOFT_LINK_CLASS(UIFoundation, NSMutableParagraphStyle)
74SOFT_LINK_CLASS(UIFoundation, NSParagraphStyle)
75SOFT_LINK_CLASS(UIFoundation, NSTextList)
76SOFT_LINK_CLASS(UIFoundation, NSTextBlock)
77SOFT_LINK_CLASS(UIFoundation, NSTextTableBlock)
78SOFT_LINK_CLASS(UIFoundation, NSTextTable)
79SOFT_LINK_CLASS(UIFoundation, NSTextTab)
80
81SOFT_LINK_CONSTANT(UIFoundation, NSFontAttributeName, NSString *)
82#define NSFontAttributeName getNSFontAttributeName()
83SOFT_LINK_CONSTANT(UIFoundation, NSForegroundColorAttributeName, NSString *)
84#define NSForegroundColorAttributeName getNSForegroundColorAttributeName()
85SOFT_LINK_CONSTANT(UIFoundation, NSBackgroundColorAttributeName, NSString *)
86#define NSBackgroundColorAttributeName getNSBackgroundColorAttributeName()
87SOFT_LINK_CONSTANT(UIFoundation, NSStrokeColorAttributeName, NSString *)
88#define NSStrokeColorAttributeName getNSStrokeColorAttributeName()
89SOFT_LINK_CONSTANT(UIFoundation, NSStrokeWidthAttributeName, NSString *)
90#define NSStrokeWidthAttributeName getNSStrokeWidthAttributeName()
91SOFT_LINK_CONSTANT(UIFoundation, NSShadowAttributeName, NSString *)
92#define NSShadowAttributeName getNSShadowAttributeName()
93SOFT_LINK_CONSTANT(UIFoundation, NSKernAttributeName, NSString *)
94#define NSKernAttributeName getNSKernAttributeName()
95SOFT_LINK_CONSTANT(UIFoundation, NSLigatureAttributeName, NSString *)
96#define NSLigatureAttributeName getNSLigatureAttributeName()
97SOFT_LINK_CONSTANT(UIFoundation, NSUnderlineStyleAttributeName, NSString *)
98#define NSUnderlineStyleAttributeName getNSUnderlineStyleAttributeName()
99SOFT_LINK_CONSTANT(UIFoundation, NSStrikethroughStyleAttributeName, NSString *)
100#define NSStrikethroughStyleAttributeName getNSStrikethroughStyleAttributeName()
101SOFT_LINK_CONSTANT(UIFoundation, NSBaselineOffsetAttributeName, NSString *)
102#define NSBaselineOffsetAttributeName getNSBaselineOffsetAttributeName()
103SOFT_LINK_CONSTANT(UIFoundation, NSWritingDirectionAttributeName, NSString *)
104#define NSWritingDirectionAttributeName getNSWritingDirectionAttributeName()
105SOFT_LINK_CONSTANT(UIFoundation, NSParagraphStyleAttributeName, NSString *)
106#define NSParagraphStyleAttributeName getNSParagraphStyleAttributeName()
107SOFT_LINK_CONSTANT(UIFoundation, NSAttachmentAttributeName, NSString *)
108#define NSAttachmentAttributeName getNSAttachmentAttributeName()
109SOFT_LINK_CONSTANT(UIFoundation, NSLinkAttributeName, NSString *)
110#define NSLinkAttributeName getNSLinkAttributeName()
111SOFT_LINK_CONSTANT(UIFoundation, NSAuthorDocumentAttribute, NSString *)
112#define NSAuthorDocumentAttribute getNSAuthorDocumentAttribute()
113SOFT_LINK_CONSTANT(UIFoundation, NSEditorDocumentAttribute, NSString *)
114#define NSEditorDocumentAttribute getNSEditorDocumentAttribute()
115SOFT_LINK_CONSTANT(UIFoundation, NSGeneratorDocumentAttribute, NSString *)
116#define NSGeneratorDocumentAttribute getNSGeneratorDocumentAttribute()
117SOFT_LINK_CONSTANT(UIFoundation, NSCompanyDocumentAttribute, NSString *)
118#define NSCompanyDocumentAttribute getNSCompanyDocumentAttribute()
119SOFT_LINK_CONSTANT(UIFoundation, NSDisplayNameDocumentAttribute, NSString *)
120#define NSDisplayNameDocumentAttribute getNSDisplayNameDocumentAttribute()
121SOFT_LINK_CONSTANT(UIFoundation, NSCopyrightDocumentAttribute, NSString *)
122#define NSCopyrightDocumentAttribute getNSCopyrightDocumentAttribute()
123SOFT_LINK_CONSTANT(UIFoundation, NSSubjectDocumentAttribute, NSString *)
124#define NSSubjectDocumentAttribute getNSSubjectDocumentAttribute()
125SOFT_LINK_CONSTANT(UIFoundation, NSCommentDocumentAttribute, NSString *)
126#define NSCommentDocumentAttribute getNSCommentDocumentAttribute()
127SOFT_LINK_CONSTANT(UIFoundation, NSNoIndexDocumentAttribute, NSString *)
128#define NSNoIndexDocumentAttribute getNSNoIndexDocumentAttribute()
129SOFT_LINK_CONSTANT(UIFoundation, NSKeywordsDocumentAttribute, NSString *)
130#define NSKeywordsDocumentAttribute getNSKeywordsDocumentAttribute()
131SOFT_LINK_CONSTANT(UIFoundation, NSCreationTimeDocumentAttribute, NSString *)
132#define NSCreationTimeDocumentAttribute getNSCreationTimeDocumentAttribute()
133SOFT_LINK_CONSTANT(UIFoundation, NSModificationTimeDocumentAttribute, NSString *)
134#define NSModificationTimeDocumentAttribute getNSModificationTimeDocumentAttribute()
135SOFT_LINK_CONSTANT(UIFoundation, NSConvertedDocumentAttribute, NSString *)
136#define NSConvertedDocumentAttribute getNSConvertedDocumentAttribute()
137SOFT_LINK_CONSTANT(UIFoundation, NSCocoaVersionDocumentAttribute, NSString *)
138#define NSCocoaVersionDocumentAttribute getNSCocoaVersionDocumentAttribute()
139
140#define PlatformNSShadow            getNSShadowClass()
141#define PlatformNSTextAttachment    getNSTextAttachmentClass()
142#define PlatformNSParagraphStyle    getNSParagraphStyleClass()
143#define PlatformNSTextList          getNSTextListClass()
144#define PlatformNSTextTableBlock    getNSTextTableBlockClass()
145#define PlatformNSTextTable         getNSTextTableClass()
146#define PlatformNSTextTab           getNSTextTabClass()
147#define PlatformColor               UIColor
148#define PlatformColorClass          getUIColorClass()
149#define PlatformNSColorClass        getNSColorClass()
150#define PlatformFont                UIFont
151#define PlatformFontClass           getUIFontClass()
152
153// We don't softlink NSSuperscriptAttributeName because UIFoundation stopped exporting it.
154// This attribute is being deprecated at the API level, but internally UIFoundation
155// will continue to support it.
156static NSString *const NSSuperscriptAttributeName = @"NSSuperscript";
157#else
158
159#define PlatformNSShadow            NSShadow
160#define PlatformNSTextAttachment    NSTextAttachment
161#define PlatformNSParagraphStyle    NSParagraphStyle
162#define PlatformNSTextList          NSTextList
163#define PlatformNSTextTableBlock    NSTextTableBlock
164#define PlatformNSTextTable         NSTextTable
165#define PlatformNSTextTab           NSTextTab
166#define PlatformColor               NSColor
167#define PlatformColorClass          NSColor
168#define PlatformNSColorClass        NSColor
169#define PlatformFont                NSFont
170#define PlatformFontClass           NSFont
171
172#define NSTextAlignmentLeft         NSLeftTextAlignment
173#define NSTextAlignmentRight        NSRightTextAlignment
174#define NSTextAlignmentCenter       NSCenterTextAlignment
175#define NSTextAlignmentJustified    NSJustifiedTextAlignment
176#endif
177
178using namespace WebCore;
179using namespace HTMLNames;
180
181#if PLATFORM(IOS)
182
183typedef enum {
184    UIFontTraitPlain       = 0x00000000,
185    UIFontTraitItalic      = 0x00000001, // 1 << 0
186    UIFontTraitBold        = 0x00000002, // 1 << 1
187    UIFontTraitThin        = (1 << 2),
188    UIFontTraitLight       = (1 << 3),
189    UIFontTraitUltraLight  = (1 << 4)
190} UIFontTrait;
191
192typedef NS_ENUM(NSInteger, NSUnderlineStyle) {
193    NSUnderlineStyleNone                                = 0x00,
194    NSUnderlineStyleSingle                              = 0x01,
195    NSUnderlineStyleThick NS_ENUM_AVAILABLE_IOS(7_0)    = 0x02,
196    NSUnderlineStyleDouble NS_ENUM_AVAILABLE_IOS(7_0)   = 0x09,
197
198    NSUnderlinePatternSolid NS_ENUM_AVAILABLE_IOS(7_0)      = 0x0000,
199    NSUnderlinePatternDot NS_ENUM_AVAILABLE_IOS(7_0)        = 0x0100,
200    NSUnderlinePatternDash NS_ENUM_AVAILABLE_IOS(7_0)       = 0x0200,
201    NSUnderlinePatternDashDot NS_ENUM_AVAILABLE_IOS(7_0)    = 0x0300,
202    NSUnderlinePatternDashDotDot NS_ENUM_AVAILABLE_IOS(7_0) = 0x0400,
203
204    NSUnderlineByWord NS_ENUM_AVAILABLE_IOS(7_0) = 0x8000
205};
206
207enum {
208    NSTextBlockAbsoluteValueType    = 0,    // Absolute value in points
209    NSTextBlockPercentageValueType  = 1     // Percentage value (out of 100)
210};
211typedef NSUInteger NSTextBlockValueType;
212
213enum {
214    NSTextBlockWidth            = 0,
215    NSTextBlockMinimumWidth     = 1,
216    NSTextBlockMaximumWidth     = 2,
217    NSTextBlockHeight           = 4,
218    NSTextBlockMinimumHeight    = 5,
219    NSTextBlockMaximumHeight    = 6
220};
221typedef NSUInteger NSTextBlockDimension;
222
223enum {
224    NSTextBlockPadding  = -1,
225    NSTextBlockBorder   =  0,
226    NSTextBlockMargin   =  1
227};
228typedef NSInteger NSTextBlockLayer;
229
230enum {
231    NSTextTableAutomaticLayoutAlgorithm = 0,
232    NSTextTableFixedLayoutAlgorithm     = 1
233};
234typedef NSUInteger NSTextTableLayoutAlgorithm;
235
236enum {
237    NSTextBlockTopAlignment         = 0,
238    NSTextBlockMiddleAlignment      = 1,
239    NSTextBlockBottomAlignment      = 2,
240    NSTextBlockBaselineAlignment    = 3
241};
242typedef NSUInteger NSTextBlockVerticalAlignment;
243
244typedef NS_ENUM(NSInteger, NSTextAlignment) {
245    NSTextAlignmentLeft      = 0,    // Visually left aligned
246    NSTextAlignmentCenter    = 1,    // Visually centered
247    NSTextAlignmentRight     = 2,    // Visually right aligned
248    NSTextAlignmentJustified = 3,    // Fully-justified. The last line in a paragraph is natural-aligned.
249    NSTextAlignmentNatural   = 4,    // Indicates the default alignment for script
250} NS_ENUM_AVAILABLE_IOS(6_0);
251
252typedef NS_ENUM(NSInteger, NSWritingDirection) {
253    NSWritingDirectionNatural       = -1,    // Determines direction using the Unicode Bidi Algorithm rules P2 and P3
254    NSWritingDirectionLeftToRight   =  0,    // Left to right writing direction
255    NSWritingDirectionRightToLeft   =  1     // Right to left writing direction
256} NS_ENUM_AVAILABLE_IOS(6_0);
257
258typedef NS_ENUM(NSInteger, NSTextWritingDirection) {
259    NSTextWritingDirectionEmbedding     = (0 << 1),
260    NSTextWritingDirectionOverride      = (1 << 1)
261} NS_ENUM_AVAILABLE_IOS(7_0);
262
263enum {
264    NSEnterCharacter                = 0x0003,
265    NSBackspaceCharacter            = 0x0008,
266    NSTabCharacter                  = 0x0009,
267    NSNewlineCharacter              = 0x000a,
268    NSFormFeedCharacter             = 0x000c,
269    NSCarriageReturnCharacter       = 0x000d,
270    NSBackTabCharacter              = 0x0019,
271    NSDeleteCharacter               = 0x007f,
272    NSLineSeparatorCharacter        = 0x2028,
273    NSParagraphSeparatorCharacter   = 0x2029,
274    NSAttachmentCharacter           = 0xFFFC // Replacement character is used for attachments
275};
276
277enum {
278    NSLeftTabStopType = 0,
279    NSRightTabStopType,
280    NSCenterTabStopType,
281    NSDecimalTabStopType
282};
283typedef NSUInteger NSTextTabType;
284
285@interface UIColor : NSObject
286+ (UIColor *)clearColor;
287- (CGFloat)alphaComponent;
288+ (UIColor *)_disambiguated_due_to_CIImage_colorWithCGColor:(CGColorRef)cgColor;
289@end
290
291@interface NSColor : UIColor
292+ (id)colorWithCalibratedRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
293@end
294
295@interface UIFont
296+ (UIFont *)fontWithName:(NSString *)fontName size:(CGFloat)fontSize;
297+ (UIFont *)fontWithFamilyName:(NSString *)familyName traits:(UIFontTrait)traits size:(CGFloat)fontSize;
298- (NSString *)familyName;
299- (CGFloat)pointSize;
300- (UIFont *)fontWithSize:(CGFloat)fontSize;
301+ (NSArray *)familyNames;
302+ (NSArray *)fontNamesForFamilyName:(NSString *)familyName;
303+ (UIFont *)systemFontOfSize:(CGFloat)fontSize;
304@end
305
306@interface NSTextTab
307- (id)initWithType:(NSTextTabType)type location:(CGFloat)loc;
308- (id)initWithTextAlignment:(NSTextAlignment)alignment location:(CGFloat)loc options:(NSDictionary *)options;
309- (CGFloat)location;
310- (void)release;
311@end
312
313@interface NSParagraphStyle : NSObject
314+ (NSParagraphStyle *)defaultParagraphStyle;
315- (void)setAlignment:(NSTextAlignment)alignment;
316- (void)setBaseWritingDirection:(NSWritingDirection)writingDirection;
317- (void)setHeadIndent:(CGFloat)aFloat;
318- (CGFloat)headIndent;
319- (void)setHeaderLevel:(NSInteger)level;
320- (void)setFirstLineHeadIndent:(CGFloat)aFloat;
321- (void)setTailIndent:(CGFloat)aFloat;
322- (void)setParagraphSpacing:(CGFloat)paragraphSpacing;
323- (void)setTextLists:(NSArray *)array;
324- (void)setTextBlocks:(NSArray *)array;
325- (void)setMinimumLineHeight:(CGFloat)aFloat;
326- (NSArray *)textLists;
327- (void)removeTabStop:(NSTextTab *)anObject;
328- (void)addTabStop:(NSTextTab *)anObject;
329- (NSArray *)tabStops;
330- (void)setHyphenationFactor:(float)aFactor;
331@end
332
333@interface NSShadow
334- (void)setShadowOffset:(CGSize)size;
335- (void)setShadowBlurRadius:(CGFloat)radius;
336- (void)setShadowColor:(UIColor *)color;
337@end
338
339@interface NSTextBlock : NSObject
340- (void)setValue:(CGFloat)val type:(NSTextBlockValueType)type forDimension:(NSTextBlockDimension)dimension;
341- (void)setWidth:(CGFloat)val type:(NSTextBlockValueType)type forLayer:(NSTextBlockLayer)layer edge:(NSRectEdge)edge;
342- (void)setBackgroundColor:(UIColor *)color;
343- (UIColor *)backgroundColor;
344- (void)setBorderColor:(UIColor *)color forEdge:(NSRectEdge)edge;
345- (void)setBorderColor:(UIColor *)color;        // Convenience method sets all edges at once
346- (void)setVerticalAlignment:(NSTextBlockVerticalAlignment)alignment;
347@end
348
349@interface NSTextList
350- (id)initWithMarkerFormat:(NSString *)format options:(NSUInteger)mask;
351- (void)setStartingItemNumber:(NSInteger)itemNum;
352- (NSInteger)startingItemNumber;
353- (NSString *)markerForItemNumber:(NSInteger)itemNum;
354- (void)release;
355@end
356
357@interface NSMutableParagraphStyle : NSParagraphStyle
358- (void)setDefaultTabInterval:(CGFloat)aFloat;
359- (void)setTabStops:(NSArray *)array;
360@end
361
362@interface NSTextAttachment : NSObject
363- (id)initWithFileWrapper:(NSFileWrapper *)fileWrapper;
364#if PLATFORM(IOS)
365- (void)setBounds:(CGRect)bounds;
366#endif
367- (void)release;
368@end
369
370@interface NSTextTable : NSTextBlock
371- (void)setNumberOfColumns:(NSUInteger)numCols;
372- (void)setCollapsesBorders:(BOOL)flag;
373- (void)setHidesEmptyCells:(BOOL)flag;
374- (void)setLayoutAlgorithm:(NSTextTableLayoutAlgorithm)algorithm;
375- (NSUInteger)numberOfColumns;
376- (void)release;
377@end
378
379@interface NSTextTableBlock : NSTextBlock
380- (id)initWithTable:(NSTextTable *)table startingRow:(NSInteger)row rowSpan:(NSInteger)rowSpan startingColumn:(NSInteger)col columnSpan:(NSInteger)colSpan;     // Designated initializer
381- (NSInteger)startingColumn;
382- (NSInteger)startingRow;
383- (NSUInteger)numberOfColumns;
384- (NSInteger)columnSpan;
385- (NSInteger)rowSpan;
386@end
387
388#else
389static NSFileWrapper *fileWrapperForURL(DocumentLoader *, NSURL *);
390static NSFileWrapper *fileWrapperForElement(Element*);
391
392@interface NSTextAttachment (WebCoreNSTextAttachment)
393- (void)setIgnoresOrientation:(BOOL)flag;
394- (void)setBounds:(CGRect)bounds;
395- (BOOL)ignoresOrientation;
396@end
397
398#endif
399
400// Additional control Unicode characters
401const unichar WebNextLineCharacter = 0x0085;
402
403static const CGFloat defaultFontSize = 12;
404static const CGFloat minimumFontSize = 1;
405
406class HTMLConverterCaches {
407public:
408    String propertyValueForNode(Node&, CSSPropertyID );
409    bool floatPropertyValueForNode(Node&, CSSPropertyID, float&);
410    Color colorPropertyValueForNode(Node&, CSSPropertyID);
411
412    bool isBlockElement(Element&);
413    bool elementHasOwnBackgroundColor(Element&);
414
415    PassRefPtr<CSSValue> computedStylePropertyForElement(Element&, CSSPropertyID);
416    PassRefPtr<CSSValue> inlineStylePropertyForElement(Element&, CSSPropertyID);
417
418    Node* cacheAncestorsOfStartToBeConverted(const Range&);
419    bool isAncestorsOfStartToBeConverted(Node& node) const { return m_ancestorsUnderCommonAncestor.contains(&node); }
420
421private:
422    HashMap<Element*, std::unique_ptr<ComputedStyleExtractor>> m_computedStyles;
423    HashSet<Node*> m_ancestorsUnderCommonAncestor;
424};
425
426@interface NSTextList (WebCoreNSTextListDetails)
427+ (NSDictionary *)_standardMarkerAttributesForAttributes:(NSDictionary *)attrs;
428@end
429
430@interface NSURL (WebCoreNSURLDetails)
431// FIXME: What is the reason to use this Foundation method, and not +[NSURL URLWithString:relativeToURL:]?
432+ (NSURL *)_web_URLWithString:(NSString *)string relativeToURL:(NSURL *)baseURL;
433@end
434
435@interface NSObject(WebMessageDocumentSimulation)
436+ (void)document:(NSObject **)outDocument attachment:(NSTextAttachment **)outAttachment forURL:(NSURL *)url;
437@end
438
439class HTMLConverter {
440public:
441    HTMLConverter(Range&);
442    ~HTMLConverter();
443
444    NSAttributedString* convert();
445
446private:
447    Ref<Range> m_range;
448    DocumentLoader* m_dataSource;
449
450    HashMap<RefPtr<Element>, RetainPtr<NSDictionary>> m_attributesForElements;
451    HashMap<RetainPtr<NSTextTable>, RefPtr<Element>> m_textTableFooters;
452    HashMap<RefPtr<Element>, RetainPtr<NSDictionary>> m_aggregatedAttributesForElements;
453
454    NSMutableAttributedString *_attrStr;
455    NSMutableDictionary *_documentAttrs;
456    NSURL *_baseURL;
457    NSMutableArray *_textLists;
458    NSMutableArray *_textBlocks;
459    NSMutableArray *_textTables;
460    NSMutableArray *_textTableSpacings;
461    NSMutableArray *_textTablePaddings;
462    NSMutableArray *_textTableRows;
463    NSMutableArray *_textTableRowArrays;
464    NSMutableArray *_textTableRowBackgroundColors;
465    NSMutableDictionary *_fontCache;
466    NSMutableArray *_writingDirectionArray;
467
468    CGFloat _defaultTabInterval;
469    NSUInteger _domRangeStartIndex;
470    NSInteger _quoteLevel;
471
472    std::unique_ptr<HTMLConverterCaches> _caches;
473
474    struct {
475        unsigned int isSoft:1;
476        unsigned int reachedStart:1;
477        unsigned int reachedEnd:1;
478        unsigned int hasTrailingNewline:1;
479        unsigned int pad:26;
480    } _flags;
481
482    PlatformColor *_colorForElement(Element&, CSSPropertyID);
483
484    void _traverseNode(Node&, unsigned depth, bool embedded);
485    void _traverseFooterNode(Element&, unsigned depth);
486
487    NSDictionary *computedAttributesForElement(Element&);
488    NSDictionary *attributesForElement(Element&);
489    NSDictionary *aggregatedAttributesForAncestors(CharacterData&);
490    NSDictionary* aggregatedAttributesForElementAndItsAncestors(Element&);
491
492    Element* _blockLevelElementForNode(Node*);
493
494    void _newParagraphForElement(Element&, NSString *tag, BOOL flag, BOOL suppressTrailingSpace);
495    void _newLineForElement(Element&);
496    void _newTabForElement(Element&);
497    BOOL _addAttachmentForElement(Element&, NSURL *url, BOOL needsParagraph, BOOL usePlaceholder);
498    void _addQuoteForElement(Element&, BOOL opening, NSInteger level);
499    void _addValue(NSString *value, Element&);
500    void _fillInBlock(NSTextBlock *block, Element&, PlatformColor *backgroundColor, CGFloat extraMargin, CGFloat extraPadding, BOOL isTable);
501
502    BOOL _enterElement(Element&, BOOL embedded);
503    BOOL _processElement(Element&, NSInteger depth);
504    void _exitElement(Element&, NSInteger depth, NSUInteger startIndex);
505
506    void _processHeadElement(Element&);
507    void _processMetaElementWithName(NSString *name, NSString *content);
508
509    void _addTableForElement(Element* tableElement);
510    void _addTableCellForElement(Element* tableCellElement);
511    void _addMarkersToList(NSTextList *list, NSRange range);
512    void _processText(CharacterData&);
513    void _adjustTrailingNewline();
514};
515
516HTMLConverter::HTMLConverter(Range& range)
517    : m_range(range)
518    , m_dataSource(nullptr)
519{
520    _attrStr = [[NSMutableAttributedString alloc] init];
521    _documentAttrs = [[NSMutableDictionary alloc] init];
522    _baseURL = nil;
523    _textLists = [[NSMutableArray alloc] init];
524    _textBlocks = [[NSMutableArray alloc] init];
525    _textTables = [[NSMutableArray alloc] init];
526    _textTableSpacings = [[NSMutableArray alloc] init];
527    _textTablePaddings = [[NSMutableArray alloc] init];
528    _textTableRows = [[NSMutableArray alloc] init];
529    _textTableRowArrays = [[NSMutableArray alloc] init];
530    _textTableRowBackgroundColors = [[NSMutableArray alloc] init];
531    _fontCache = [[NSMutableDictionary alloc] init];
532    _writingDirectionArray = [[NSMutableArray alloc] init];
533
534    _defaultTabInterval = 36;
535    _domRangeStartIndex = 0;
536    _quoteLevel = 0;
537
538    _flags.isSoft = false;
539    _flags.reachedStart = false;
540    _flags.reachedEnd = false;
541
542    _caches = std::make_unique<HTMLConverterCaches>();
543}
544
545HTMLConverter::~HTMLConverter()
546{
547    [_attrStr release];
548    [_documentAttrs release];
549    [_textLists release];
550    [_textBlocks release];
551    [_textTables release];
552    [_textTableSpacings release];
553    [_textTablePaddings release];
554    [_textTableRows release];
555    [_textTableRowArrays release];
556    [_textTableRowBackgroundColors release];
557    [_fontCache release];
558    [_writingDirectionArray release];
559}
560
561NSAttributedString *HTMLConverter::convert()
562{
563    Node* commonAncestorContainer = _caches->cacheAncestorsOfStartToBeConverted(m_range.get());
564    ASSERT(commonAncestorContainer);
565
566    m_dataSource = commonAncestorContainer->document().frame()->loader().documentLoader();
567    if (!m_dataSource)
568        return nil;
569
570    _domRangeStartIndex = 0;
571    _traverseNode(*commonAncestorContainer, 0, false /* embedded */);
572    if (_domRangeStartIndex > 0 && _domRangeStartIndex <= [_attrStr length])
573        [_attrStr deleteCharactersInRange:NSMakeRange(0, _domRangeStartIndex)];
574
575    return [[_attrStr retain] autorelease];
576}
577
578#if !PLATFORM(IOS)
579// Returns the font to be used if the NSFontAttributeName doesn't exist
580static NSFont *WebDefaultFont()
581{
582    static NSFont *defaultFont = nil;
583    if (defaultFont)
584        return defaultFont;
585
586    NSFont *font = [NSFont fontWithName:@"Helvetica" size:12];
587    if (!font)
588        font = [NSFont systemFontOfSize:12];
589
590    defaultFont = [font retain];
591    return defaultFont;
592}
593#endif
594
595static PlatformFont *_fontForNameAndSize(NSString *fontName, CGFloat size, NSMutableDictionary *cache)
596{
597    PlatformFont *font = [cache objectForKey:fontName];
598#if PLATFORM(IOS)
599    if (font)
600        return [font fontWithSize:size];
601
602    font = [PlatformFontClass fontWithName:fontName size:size];
603#else
604    NSFontManager *fontManager = [NSFontManager sharedFontManager];
605    if (font) {
606        font = [fontManager convertFont:font toSize:size];
607        return font;
608    }
609    font = [fontManager fontWithFamily:fontName traits:0 weight:0 size:size];
610#endif
611    if (!font) {
612#if PLATFORM(IOS)
613        NSArray *availableFamilyNames = [PlatformFontClass familyNames];
614#else
615        NSArray *availableFamilyNames = [fontManager availableFontFamilies];
616#endif
617        NSRange dividingRange;
618        NSRange dividingSpaceRange = [fontName rangeOfString:@" " options:NSBackwardsSearch];
619        NSRange dividingDashRange = [fontName rangeOfString:@"-" options:NSBackwardsSearch];
620        dividingRange = (0 < dividingSpaceRange.length && 0 < dividingDashRange.length) ? (dividingSpaceRange.location > dividingDashRange.location ? dividingSpaceRange : dividingDashRange) : (0 < dividingSpaceRange.length ? dividingSpaceRange : dividingDashRange);
621
622        while (dividingRange.length > 0) {
623            NSString *familyName = [fontName substringToIndex:dividingRange.location];
624            if ([availableFamilyNames containsObject:familyName]) {
625#if PLATFORM(IOS)
626                NSString *faceName = [fontName substringFromIndex:(dividingRange.location + dividingRange.length)];
627                NSArray *familyMemberFaceNames = [PlatformFontClass fontNamesForFamilyName:familyName];
628                for (NSString *familyMemberFaceName in familyMemberFaceNames) {
629                    if ([familyMemberFaceName compare:faceName options:NSCaseInsensitiveSearch] == NSOrderedSame) {
630                        font = [PlatformFontClass fontWithName:familyMemberFaceName size:size];
631                        break;
632                    }
633                }
634                if (!font && [familyMemberFaceNames count])
635                    font = [getUIFontClass() fontWithName:familyName size:size];
636#else
637                NSArray *familyMemberArray;
638                NSString *faceName = [fontName substringFromIndex:(dividingRange.location + dividingRange.length)];
639                NSArray *familyMemberArrays = [fontManager availableMembersOfFontFamily:familyName];
640                NSEnumerator *familyMemberArraysEnum = [familyMemberArrays objectEnumerator];
641                while ((familyMemberArray = [familyMemberArraysEnum nextObject])) {
642                    NSString *familyMemberFaceName = [familyMemberArray objectAtIndex:1];
643                    if ([familyMemberFaceName compare:faceName options:NSCaseInsensitiveSearch] == NSOrderedSame) {
644                        NSFontTraitMask traits = [[familyMemberArray objectAtIndex:3] integerValue];
645                        NSInteger weight = [[familyMemberArray objectAtIndex:2] integerValue];
646                        font = [fontManager fontWithFamily:familyName traits:traits weight:weight size:size];
647                        break;
648                    }
649                }
650                if (!font) {
651                    if (0 < [familyMemberArrays count]) {
652                        NSArray *familyMemberArray = [familyMemberArrays objectAtIndex:0];
653                        NSFontTraitMask traits = [[familyMemberArray objectAtIndex:3] integerValue];
654                        NSInteger weight = [[familyMemberArray objectAtIndex:2] integerValue];
655                        font = [fontManager fontWithFamily:familyName traits:traits weight:weight size:size];
656                    }
657                }
658#endif
659                break;
660            } else {
661                dividingSpaceRange = [familyName rangeOfString:@" " options:NSBackwardsSearch];
662                dividingDashRange = [familyName rangeOfString:@"-" options:NSBackwardsSearch];
663                dividingRange = (0 < dividingSpaceRange.length && 0 < dividingDashRange.length) ? (dividingSpaceRange.location > dividingDashRange.location ? dividingSpaceRange : dividingDashRange) : (0 < dividingSpaceRange.length ? dividingSpaceRange : dividingDashRange);
664            }
665        }
666    }
667#if PLATFORM(IOS)
668    if (!font)
669        font = [PlatformFontClass systemFontOfSize:size];
670#else
671    if (!font)
672        font = [NSFont fontWithName:@"Times" size:size];
673    if (!font)
674        font = [NSFont userFontOfSize:size];
675    if (!font)
676        font = [fontManager convertFont:WebDefaultFont() toSize:size];
677    if (!font)
678        font = WebDefaultFont();
679#endif
680    [cache setObject:font forKey:fontName];
681
682    return font;
683}
684
685static NSParagraphStyle *defaultParagraphStyle()
686{
687    static NSMutableParagraphStyle *defaultParagraphStyle = nil;
688    if (!defaultParagraphStyle) {
689        defaultParagraphStyle = [[PlatformNSParagraphStyle defaultParagraphStyle] mutableCopy];
690        [defaultParagraphStyle setDefaultTabInterval:36];
691        [defaultParagraphStyle setTabStops:[NSArray array]];
692    }
693    return defaultParagraphStyle;
694}
695
696PassRefPtr<CSSValue> HTMLConverterCaches::computedStylePropertyForElement(Element& element, CSSPropertyID propertyId)
697{
698    if (propertyId == CSSPropertyInvalid)
699        return nullptr;
700
701    auto result = m_computedStyles.add(&element, nullptr);
702    if (result.isNewEntry)
703        result.iterator->value = std::make_unique<ComputedStyleExtractor>(&element, true);
704    ComputedStyleExtractor& computedStyle = *result.iterator->value;
705    return computedStyle.propertyValue(propertyId);
706}
707
708PassRefPtr<CSSValue> HTMLConverterCaches::inlineStylePropertyForElement(Element& element, CSSPropertyID propertyId)
709{
710    if (propertyId == CSSPropertyInvalid || !element.isStyledElement())
711        return nullptr;
712    const StyleProperties* properties = toStyledElement(element).inlineStyle();
713    if (!properties)
714        return nullptr;
715    return properties->getPropertyCSSValue(propertyId);
716}
717
718static bool stringFromCSSValue(CSSValue& value, String& result)
719{
720    if (value.isPrimitiveValue()) {
721        unsigned short primitiveType = toCSSPrimitiveValue(value).primitiveType();
722        if (primitiveType == CSSPrimitiveValue::CSS_STRING || primitiveType == CSSPrimitiveValue::CSS_URI ||
723            primitiveType == CSSPrimitiveValue::CSS_IDENT || primitiveType == CSSPrimitiveValue::CSS_ATTR) {
724            String stringValue = value.cssText();
725            if (stringValue.length()) {
726                result = stringValue;
727                return true;
728            }
729        }
730    } else if (value.isValueList()) {
731        result = value.cssText();
732        return true;
733    }
734    return false;
735}
736
737String HTMLConverterCaches::propertyValueForNode(Node& node, CSSPropertyID propertyId)
738{
739    if (!node.isElementNode()) {
740        if (Node* parent = node.parentNode())
741            return propertyValueForNode(*parent, propertyId);
742        return String();
743    }
744
745    bool inherit = false;
746    Element& element = toElement(node);
747    if (RefPtr<CSSValue> value = computedStylePropertyForElement(element, propertyId)) {
748        String result;
749        if (stringFromCSSValue(*value, result))
750            return result;
751    }
752
753    if (RefPtr<CSSValue> value = inlineStylePropertyForElement(element, propertyId)) {
754        String result;
755        if (value->isInheritedValue())
756            inherit = true;
757        else if (stringFromCSSValue(*value, result))
758            return result;
759    }
760
761    switch (propertyId) {
762    case CSSPropertyDisplay:
763        if (element.hasTagName(headTag) || element.hasTagName(scriptTag) || element.hasTagName(appletTag) || element.hasTagName(noframesTag))
764            return "none";
765        else if (element.hasTagName(addressTag) || element.hasTagName(blockquoteTag) || element.hasTagName(bodyTag) || element.hasTagName(centerTag)
766             || element.hasTagName(ddTag) || element.hasTagName(dirTag) || element.hasTagName(divTag) || element.hasTagName(dlTag)
767             || element.hasTagName(dtTag) || element.hasTagName(fieldsetTag) || element.hasTagName(formTag) || element.hasTagName(frameTag)
768             || element.hasTagName(framesetTag) || element.hasTagName(hrTag) || element.hasTagName(htmlTag) || element.hasTagName(h1Tag)
769             || element.hasTagName(h2Tag) || element.hasTagName(h3Tag) || element.hasTagName(h4Tag) || element.hasTagName(h5Tag)
770             || element.hasTagName(h6Tag) || element.hasTagName(iframeTag) || element.hasTagName(menuTag) || element.hasTagName(noscriptTag)
771             || element.hasTagName(olTag) || element.hasTagName(pTag) || element.hasTagName(preTag) || element.hasTagName(ulTag))
772            return "block";
773        else if (element.hasTagName(liTag))
774            return "list-item";
775        else if (element.hasTagName(tableTag))
776            return "table";
777        else if (element.hasTagName(trTag))
778            return "table-row";
779        else if (element.hasTagName(thTag) || element.hasTagName(tdTag))
780            return "table-cell";
781        else if (element.hasTagName(theadTag))
782            return "table-header-group";
783        else if (element.hasTagName(tbodyTag))
784            return "table-row-group";
785        else if (element.hasTagName(tfootTag))
786            return "table-footer-group";
787        else if (element.hasTagName(colTag))
788            return "table-column";
789        else if (element.hasTagName(colgroupTag))
790            return "table-column-group";
791        else if (element.hasTagName(captionTag))
792            return "table-caption";
793        break;
794    case CSSPropertyWhiteSpace:
795        if (element.hasTagName(preTag))
796            return "pre";
797        inherit = true;
798        break;
799    case CSSPropertyFontStyle:
800        if (element.hasTagName(iTag) || element.hasTagName(citeTag) || element.hasTagName(emTag) || element.hasTagName(varTag) || element.hasTagName(addressTag))
801            return "italic";
802        inherit = true;
803        break;
804    case CSSPropertyFontWeight:
805        if (element.hasTagName(bTag) || element.hasTagName(strongTag) || element.hasTagName(thTag))
806            return "bolder";
807        inherit = true;
808        break;
809    case CSSPropertyTextDecoration:
810        if (element.hasTagName(uTag) || element.hasTagName(insTag))
811            return "underline";
812        else if (element.hasTagName(sTag) || element.hasTagName(strikeTag) || element.hasTagName(delTag))
813            return "line-through";
814        inherit = true; // FIXME: This is not strictly correct
815        break;
816    case CSSPropertyTextAlign:
817        if (element.hasTagName(centerTag) || element.hasTagName(captionTag) || element.hasTagName(thTag))
818            return "center";
819        inherit = true;
820        break;
821    case CSSPropertyVerticalAlign:
822        if (element.hasTagName(supTag))
823            return "super";
824        else if (element.hasTagName(subTag))
825            return "sub";
826        else if (element.hasTagName(theadTag) || element.hasTagName(tbodyTag) || element.hasTagName(tfootTag))
827            return "middle";
828        else if (element.hasTagName(trTag) || element.hasTagName(thTag) || element.hasTagName(tdTag))
829            inherit = true;
830        break;
831    case CSSPropertyFontFamily:
832    case CSSPropertyFontVariant:
833    case CSSPropertyTextTransform:
834    case CSSPropertyTextShadow:
835    case CSSPropertyVisibility:
836    case CSSPropertyBorderCollapse:
837    case CSSPropertyEmptyCells:
838    case CSSPropertyWordSpacing:
839    case CSSPropertyListStyleType:
840    case CSSPropertyDirection:
841        inherit = true; // FIXME: Let classes in the css component figure this out.
842        break;
843    default:
844        break;
845    }
846
847    if (inherit) {
848        if (Node* parent = node.parentNode())
849            return propertyValueForNode(*parent, propertyId);
850    }
851
852    return String();
853}
854
855static inline bool floatValueFromPrimitiveValue(CSSPrimitiveValue& primitiveValue, float& result)
856{
857    // FIXME: Use CSSPrimitiveValue::computeValue.
858    switch (primitiveValue.primitiveType()) {
859    case CSSPrimitiveValue::CSS_PX:
860        result = primitiveValue.getFloatValue(CSSPrimitiveValue::CSS_PX);
861        return true;
862    case CSSPrimitiveValue::CSS_PT:
863        result = 4 * primitiveValue.getFloatValue(CSSPrimitiveValue::CSS_PT) / 3;
864        return true;
865    case CSSPrimitiveValue::CSS_PC:
866        result = 16 * primitiveValue.getFloatValue(CSSPrimitiveValue::CSS_PC);
867        return true;
868    case CSSPrimitiveValue::CSS_CM:
869        result = 96 * primitiveValue.getFloatValue(CSSPrimitiveValue::CSS_PC) / 2.54;
870        return true;
871    case CSSPrimitiveValue::CSS_MM:
872        result = 96 * primitiveValue.getFloatValue(CSSPrimitiveValue::CSS_PC) / 25.4;
873        return true;
874    case CSSPrimitiveValue::CSS_IN:
875        result = 96 * primitiveValue.getFloatValue(CSSPrimitiveValue::CSS_IN);
876        return true;
877    default:
878        return false;
879    }
880}
881
882bool HTMLConverterCaches::floatPropertyValueForNode(Node& node, CSSPropertyID propertyId, float& result)
883{
884    if (!node.isElementNode()) {
885        if (ContainerNode* parent = node.parentNode())
886            return floatPropertyValueForNode(*parent, propertyId, result);
887        return false;
888    }
889
890    Element& element = toElement(node);
891    if (RefPtr<CSSValue> value = computedStylePropertyForElement(element, propertyId)) {
892        if (value->isPrimitiveValue() && floatValueFromPrimitiveValue(toCSSPrimitiveValue(*value), result))
893            return true;
894    }
895
896    bool inherit = false;
897    if (RefPtr<CSSValue> value = inlineStylePropertyForElement(element, propertyId)) {
898        if (value->isPrimitiveValue() && floatValueFromPrimitiveValue(toCSSPrimitiveValue(*value), result))
899            return true;
900        if (value->isInheritedValue())
901            inherit = true;
902    }
903
904    switch (propertyId) {
905    case CSSPropertyTextIndent:
906    case CSSPropertyLetterSpacing:
907    case CSSPropertyWordSpacing:
908    case CSSPropertyLineHeight:
909    case CSSPropertyWidows:
910    case CSSPropertyOrphans:
911        inherit = true;
912        break;
913    default:
914        break;
915    }
916
917    if (inherit) {
918        if (ContainerNode* parent = node.parentNode())
919            return floatPropertyValueForNode(*parent, propertyId, result);
920    }
921
922    return false;
923}
924
925#if PLATFORM(IOS)
926static NSString *_NSFirstPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde)
927{
928    NSArray *array = NSSearchPathForDirectoriesInDomains(directory, domainMask, expandTilde);
929    return [array count] >= 1 ? [array objectAtIndex:0] : nil;
930}
931
932static NSString *_NSSystemLibraryPath(void)
933{
934    return _NSFirstPathForDirectoriesInDomains(NSLibraryDirectory, NSSystemDomainMask, YES);
935}
936
937static NSBundle *_webKitBundle()
938{
939    // FIXME: This should probably use the WebCore bundle to avoid the layering violation.
940    NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.apple.WebKit"];
941    if (!bundle)
942        bundle = [NSBundle bundleWithPath:[_NSSystemLibraryPath() stringByAppendingPathComponent:@"Frameworks/WebKit.framework"]];
943    return bundle;
944}
945
946static inline UIColor *_platformColor(Color color)
947{
948    return [getUIColorClass() _disambiguated_due_to_CIImage_colorWithCGColor:cachedCGColor(color, WebCore::ColorSpaceDeviceRGB)];
949}
950#else
951static inline NSColor *_platformColor(Color color)
952{
953    return nsColor(color);
954}
955#endif
956
957static inline NSShadow *_shadowForShadowStyle(NSString *shadowStyle)
958{
959    NSShadow *shadow = nil;
960    NSUInteger shadowStyleLength = [shadowStyle length];
961    NSRange openParenRange = [shadowStyle rangeOfString:@"("];
962    NSRange closeParenRange = [shadowStyle rangeOfString:@")"];
963    NSRange firstRange = NSMakeRange(NSNotFound, 0);
964    NSRange secondRange = NSMakeRange(NSNotFound, 0);
965    NSRange thirdRange = NSMakeRange(NSNotFound, 0);
966    NSRange spaceRange;
967    if (openParenRange.length > 0 && closeParenRange.length > 0 && NSMaxRange(openParenRange) < closeParenRange.location) {
968        NSArray *components = [[shadowStyle substringWithRange:NSMakeRange(NSMaxRange(openParenRange), closeParenRange.location - NSMaxRange(openParenRange))] componentsSeparatedByString:@","];
969        if ([components count] >= 3) {
970            CGFloat red = [[components objectAtIndex:0] floatValue] / 255;
971            CGFloat green = [[components objectAtIndex:1] floatValue] / 255;
972            CGFloat blue = [[components objectAtIndex:2] floatValue] / 255;
973            CGFloat alpha = ([components count] >= 4) ? [[components objectAtIndex:3] floatValue] / 255 : 1;
974            NSColor *shadowColor = [PlatformNSColorClass colorWithCalibratedRed:red green:green blue:blue alpha:alpha];
975            NSSize shadowOffset;
976            CGFloat shadowBlurRadius;
977            firstRange = [shadowStyle rangeOfString:@"px"];
978            if (firstRange.length > 0 && NSMaxRange(firstRange) < shadowStyleLength)
979                secondRange = [shadowStyle rangeOfString:@"px" options:0 range:NSMakeRange(NSMaxRange(firstRange), shadowStyleLength - NSMaxRange(firstRange))];
980            if (secondRange.length > 0 && NSMaxRange(secondRange) < shadowStyleLength)
981                thirdRange = [shadowStyle rangeOfString:@"px" options:0 range:NSMakeRange(NSMaxRange(secondRange), shadowStyleLength - NSMaxRange(secondRange))];
982            if (firstRange.location > 0 && firstRange.length > 0 && secondRange.length > 0 && thirdRange.length > 0) {
983                spaceRange = [shadowStyle rangeOfString:@" " options:NSBackwardsSearch range:NSMakeRange(0, firstRange.location)];
984                if (spaceRange.length == 0)
985                    spaceRange = NSMakeRange(0, 0);
986                shadowOffset.width = [[shadowStyle substringWithRange:NSMakeRange(NSMaxRange(spaceRange), firstRange.location - NSMaxRange(spaceRange))] floatValue];
987                spaceRange = [shadowStyle rangeOfString:@" " options:NSBackwardsSearch range:NSMakeRange(0, secondRange.location)];
988                if (!spaceRange.length)
989                    spaceRange = NSMakeRange(0, 0);
990                CGFloat shadowHeight = [[shadowStyle substringWithRange:NSMakeRange(NSMaxRange(spaceRange), secondRange.location - NSMaxRange(spaceRange))] floatValue];
991                // I don't know why we have this difference between the two platforms.
992#if PLATFORM(IOS)
993                shadowOffset.height = shadowHeight;
994#else
995                shadowOffset.height = -shadowHeight;
996#endif
997                spaceRange = [shadowStyle rangeOfString:@" " options:NSBackwardsSearch range:NSMakeRange(0, thirdRange.location)];
998                if (!spaceRange.length)
999                    spaceRange = NSMakeRange(0, 0);
1000                shadowBlurRadius = [[shadowStyle substringWithRange:NSMakeRange(NSMaxRange(spaceRange), thirdRange.location - NSMaxRange(spaceRange))] floatValue];
1001                shadow = [[[PlatformNSShadow alloc] init] autorelease];
1002                [shadow setShadowColor:shadowColor];
1003                [shadow setShadowOffset:shadowOffset];
1004                [shadow setShadowBlurRadius:shadowBlurRadius];
1005            }
1006        }
1007    }
1008    return shadow;
1009}
1010
1011bool HTMLConverterCaches::isBlockElement(Element& element)
1012{
1013    String displayValue = propertyValueForNode(element, CSSPropertyDisplay);
1014    if (displayValue == "block" || displayValue == "list-item" || displayValue.startsWith("table"))
1015        return true;
1016    String floatValue = propertyValueForNode(element, CSSPropertyFloat);
1017    if (floatValue == "left" || floatValue == "right")
1018        return true;
1019    return false;
1020}
1021
1022bool HTMLConverterCaches::elementHasOwnBackgroundColor(Element& element)
1023{
1024    if (!isBlockElement(element))
1025        return false;
1026    // In the text system, text blocks (table elements) and documents (body elements)
1027    // have their own background colors, which should not be inherited.
1028    return element.hasTagName(htmlTag) || element.hasTagName(bodyTag) || propertyValueForNode(element, CSSPropertyDisplay).startsWith("table");
1029}
1030
1031Element* HTMLConverter::_blockLevelElementForNode(Node* node)
1032{
1033    Element* element = node->parentElement();
1034    if (element && !_caches->isBlockElement(*element))
1035        element = _blockLevelElementForNode(element->parentNode());
1036    return element;
1037}
1038
1039static Color normalizedColor(Color color, bool ignoreBlack)
1040{
1041    const double ColorEpsilon = 1 / (2 * (double)255.0);
1042
1043    double red, green, blue, alpha;
1044    color.getRGBA(red, green, blue, alpha);
1045    if (red < ColorEpsilon && green < ColorEpsilon && blue < ColorEpsilon && (ignoreBlack || alpha < ColorEpsilon))
1046        return Color();
1047
1048    return color;
1049}
1050
1051Color HTMLConverterCaches::colorPropertyValueForNode(Node& node, CSSPropertyID propertyId)
1052{
1053    if (!node.isElementNode()) {
1054        if (Node* parent = node.parentNode())
1055            return colorPropertyValueForNode(*parent, propertyId);
1056        return Color();
1057    }
1058
1059    Element& element = toElement(node);
1060    if (RefPtr<CSSValue> value = computedStylePropertyForElement(element, propertyId)) {
1061        if (value->isPrimitiveValue() && toCSSPrimitiveValue(*value).isRGBColor())
1062            return normalizedColor(Color(toCSSPrimitiveValue(*value).getRGBA32Value()), propertyId == CSSPropertyColor);
1063    }
1064
1065    bool inherit = false;
1066    if (RefPtr<CSSValue> value = inlineStylePropertyForElement(element, propertyId)) {
1067        if (value->isPrimitiveValue() && toCSSPrimitiveValue(*value).isRGBColor())
1068            return normalizedColor(Color(toCSSPrimitiveValue(*value).getRGBA32Value()), propertyId == CSSPropertyColor);
1069        if (value->isInheritedValue())
1070            inherit = true;
1071    }
1072
1073    switch (propertyId) {
1074    case CSSPropertyColor:
1075        inherit = true;
1076        break;
1077    case CSSPropertyBackgroundColor:
1078        if (!elementHasOwnBackgroundColor(element)) {
1079            if (Element* parentElement = node.parentElement()) {
1080                if (!elementHasOwnBackgroundColor(*parentElement))
1081                    inherit = true;
1082            }
1083        }
1084        break;
1085    default:
1086        break;
1087    }
1088
1089    if (inherit) {
1090        if (Node* parent = node.parentNode())
1091            return colorPropertyValueForNode(*parent, propertyId);
1092    }
1093
1094    return Color();
1095}
1096
1097PlatformColor *HTMLConverter::_colorForElement(Element& element, CSSPropertyID propertyId)
1098{
1099    Color result = _caches->colorPropertyValueForNode(element, propertyId);
1100    if (!result.isValid())
1101        return nil;
1102    PlatformColor *platformResult = _platformColor(result);
1103    if ([[PlatformColorClass clearColor] isEqual:platformResult] || ([platformResult alphaComponent] == 0.0))
1104        return nil;
1105    return platformResult;
1106}
1107
1108#if !PLATFORM(IOS)
1109static PlatformFont *_font(Element& element)
1110{
1111    auto renderer = element.renderer();
1112    if (!renderer)
1113        return nil;
1114    return renderer->style().font().primaryFont()->getNSFont();
1115}
1116#else
1117static PlatformFont *_font(Element& element)
1118{
1119    auto renderer = element.renderer();
1120    if (!renderer)
1121        return nil;
1122    return (PlatformFont *)renderer->style().font().primaryFont()->getCTFont();
1123}
1124#endif
1125
1126#define UIFloatIsZero(number) (fabs(number - 0) < FLT_EPSILON)
1127
1128NSDictionary *HTMLConverter::computedAttributesForElement(Element& element)
1129{
1130    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
1131#if !PLATFORM(IOS)
1132    NSFontManager *fontManager = [NSFontManager sharedFontManager];
1133#endif
1134
1135    PlatformFont *font = nil;
1136    PlatformFont *actualFont = _font(element);
1137    PlatformColor *foregroundColor = _colorForElement(element, CSSPropertyColor);
1138    PlatformColor *backgroundColor = _colorForElement(element, CSSPropertyBackgroundColor);
1139    PlatformColor *strokeColor = _colorForElement(element, CSSPropertyWebkitTextStrokeColor);
1140
1141    float fontSize = 0;
1142    if (!_caches->floatPropertyValueForNode(element, CSSPropertyFontSize, fontSize) || fontSize <= 0.0)
1143        fontSize = defaultFontSize;
1144    if (fontSize < minimumFontSize)
1145        fontSize = minimumFontSize;
1146    if (fabs(floor(2.0 * fontSize + 0.5) / 2.0 - fontSize) < 0.05)
1147        fontSize = floor(2.0 * fontSize + 0.5) / 2;
1148    else if (fabs(floor(10.0 * fontSize + 0.5) / 10.0 - fontSize) < 0.005)
1149        fontSize = floor(10.0 * fontSize + 0.5) / 10;
1150
1151    if (fontSize <= 0.0)
1152        fontSize = defaultFontSize;
1153
1154#if PLATFORM(IOS)
1155    if (actualFont)
1156        font = [actualFont fontWithSize:fontSize];
1157#else
1158    if (actualFont)
1159        font = [fontManager convertFont:actualFont toSize:fontSize];
1160#endif
1161    if (!font) {
1162        String fontName = _caches->propertyValueForNode(element, CSSPropertyFontFamily);
1163        if (fontName.length())
1164            font = _fontForNameAndSize(fontName.upper(), fontSize, _fontCache);
1165        if (!font)
1166            font = [PlatformFontClass fontWithName:@"Times" size:fontSize];
1167
1168        String fontStyle = _caches->propertyValueForNode(element, CSSPropertyFontStyle);
1169        if (fontStyle == "italic" || fontStyle == "oblique") {
1170            PlatformFont *originalFont = font;
1171#if PLATFORM(IOS)
1172            font = [PlatformFontClass fontWithFamilyName:[font familyName] traits:UIFontTraitItalic size:[font pointSize]];
1173#else
1174            font = [fontManager convertFont:font toHaveTrait:NSItalicFontMask];
1175#endif
1176            if (!font)
1177                font = originalFont;
1178        }
1179
1180        String fontWeight = _caches->propertyValueForNode(element, CSSPropertyFontStyle);
1181        if (fontWeight.startsWith("bold") || fontWeight.toInt() >= 700) {
1182            // ??? handle weight properly using NSFontManager
1183            PlatformFont *originalFont = font;
1184#if PLATFORM(IOS)
1185            font = [PlatformFontClass fontWithFamilyName:[font familyName] traits:UIFontTraitBold size:[font pointSize]];
1186#else
1187            font = [fontManager convertFont:font toHaveTrait:NSBoldFontMask];
1188#endif
1189            if (!font)
1190                font = originalFont;
1191        }
1192#if !PLATFORM(IOS) // IJB: No small caps support on iOS
1193        if (_caches->propertyValueForNode(element, CSSPropertyFontVariant) == "small-caps") {
1194            // ??? synthesize small-caps if [font isEqual:originalFont]
1195            NSFont *originalFont = font;
1196            font = [fontManager convertFont:font toHaveTrait:NSSmallCapsFontMask];
1197            if (!font)
1198                font = originalFont;
1199        }
1200#endif
1201    }
1202    if (font)
1203        [attrs setObject:font forKey:NSFontAttributeName];
1204    if (foregroundColor)
1205        [attrs setObject:foregroundColor forKey:NSForegroundColorAttributeName];
1206    if (backgroundColor && !_caches->elementHasOwnBackgroundColor(element))
1207        [attrs setObject:backgroundColor forKey:NSBackgroundColorAttributeName];
1208
1209    float strokeWidth = 0.0;
1210    if (_caches->floatPropertyValueForNode(element, CSSPropertyWebkitTextStrokeWidth, strokeWidth)) {
1211        float textStrokeWidth = strokeWidth / ([font pointSize] * 0.01);
1212        [attrs setObject:[NSNumber numberWithDouble:textStrokeWidth] forKey:NSStrokeWidthAttributeName];
1213    }
1214    if (strokeColor)
1215        [attrs setObject:strokeColor forKey:NSStrokeColorAttributeName];
1216
1217    String fontKerning = _caches->propertyValueForNode(element, CSSPropertyWebkitFontKerning);
1218    String letterSpacing = _caches->propertyValueForNode(element, CSSPropertyLetterSpacing);
1219    if (fontKerning.length() || letterSpacing.length()) {
1220        if (fontKerning == "none")
1221            [attrs setObject:@0.0 forKey:NSKernAttributeName];
1222        else {
1223            double kernVal = letterSpacing.length() ? letterSpacing.toDouble() : 0.0;
1224            if (UIFloatIsZero(kernVal))
1225                [attrs setObject:@0.0 forKey:NSKernAttributeName]; // auto and normal, the other possible values, are both "kerning enabled"
1226            else
1227                [attrs setObject:[NSNumber numberWithDouble:kernVal] forKey:NSKernAttributeName];
1228        }
1229    }
1230
1231    String fontLigatures = _caches->propertyValueForNode(element, CSSPropertyWebkitFontVariantLigatures);
1232    if (fontLigatures.length()) {
1233        if (fontLigatures.contains("normal"))
1234            ;   // default: whatever the system decides to do
1235        else if (fontLigatures.contains("common-ligatures"))
1236            [attrs setObject:@1 forKey:NSLigatureAttributeName];   // explicitly enabled
1237        else if (fontLigatures.contains("no-common-ligatures"))
1238            [attrs setObject:@0 forKey:NSLigatureAttributeName];  // explicitly disabled
1239    }
1240
1241    String textDecoration = _caches->propertyValueForNode(element, CSSPropertyTextDecoration);
1242    if (textDecoration.length()) {
1243        if (textDecoration.contains("underline"))
1244            [attrs setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName];
1245        if (textDecoration.contains("line-through"))
1246            [attrs setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName];
1247    }
1248
1249    String verticalAlign = _caches->propertyValueForNode(element, CSSPropertyVerticalAlign);
1250    if (verticalAlign.length()) {
1251        if (verticalAlign == "super")
1252            [attrs setObject:[NSNumber numberWithInteger:1] forKey:NSSuperscriptAttributeName];
1253        else if (verticalAlign == "sub")
1254            [attrs setObject:[NSNumber numberWithInteger:-1] forKey:NSSuperscriptAttributeName];
1255    }
1256
1257    float baselineOffset = 0.0;
1258    if (_caches->floatPropertyValueForNode(element, CSSPropertyVerticalAlign, baselineOffset))
1259        [attrs setObject:[NSNumber numberWithDouble:baselineOffset] forKey:NSBaselineOffsetAttributeName];
1260
1261    String textShadow = _caches->propertyValueForNode(element, CSSPropertyTextShadow);
1262    if (textShadow.length() > 4) {
1263        NSShadow *shadow = _shadowForShadowStyle(textShadow);
1264        if (shadow)
1265            [attrs setObject:shadow forKey:NSShadowAttributeName];
1266    }
1267
1268    Element* blockElement = _blockLevelElementForNode(&element);
1269    if (&element != blockElement && [_writingDirectionArray count] > 0)
1270        [attrs setObject:[NSArray arrayWithArray:_writingDirectionArray] forKey:NSWritingDirectionAttributeName];
1271
1272    if (blockElement) {
1273        Element& coreBlockElement = *blockElement;
1274        NSMutableParagraphStyle *paragraphStyle = [defaultParagraphStyle() mutableCopy];
1275        unsigned heading = 0;
1276        if (coreBlockElement.hasTagName(h1Tag))
1277            heading = 1;
1278        else if (coreBlockElement.hasTagName(h2Tag))
1279            heading = 2;
1280        else if (coreBlockElement.hasTagName(h3Tag))
1281            heading = 3;
1282        else if (coreBlockElement.hasTagName(h4Tag))
1283            heading = 4;
1284        else if (coreBlockElement.hasTagName(h5Tag))
1285            heading = 5;
1286        else if (coreBlockElement.hasTagName(h6Tag))
1287            heading = 6;
1288        bool isParagraph = coreBlockElement.hasTagName(pTag) || coreBlockElement.hasTagName(liTag) || heading;
1289
1290        String textAlign = _caches->propertyValueForNode(coreBlockElement, CSSPropertyTextAlign);
1291        if (textAlign.length()) {
1292            // WebKit can return -khtml-left, -khtml-right, -khtml-center
1293            if (textAlign.endsWith("left"))
1294                [paragraphStyle setAlignment:NSTextAlignmentLeft];
1295            else if (textAlign.endsWith("right"))
1296                [paragraphStyle setAlignment:NSTextAlignmentRight];
1297            else if (textAlign.endsWith("center"))
1298                [paragraphStyle setAlignment:NSTextAlignmentCenter];
1299            else if (textAlign.endsWith("justify"))
1300                [paragraphStyle setAlignment:NSTextAlignmentJustified];
1301        }
1302
1303        String direction = _caches->propertyValueForNode(coreBlockElement, CSSPropertyDirection);
1304        if (direction.length()) {
1305            if (direction == "ltr")
1306                [paragraphStyle setBaseWritingDirection:NSWritingDirectionLeftToRight];
1307            else if (direction == "rtl")
1308                [paragraphStyle setBaseWritingDirection:NSWritingDirectionRightToLeft];
1309        }
1310
1311        String hyphenation = _caches->propertyValueForNode(coreBlockElement, CSSPropertyWebkitHyphens);
1312        if (hyphenation.length()) {
1313            if (hyphenation == "auto")
1314                [paragraphStyle setHyphenationFactor:1.0];
1315            else
1316                [paragraphStyle setHyphenationFactor:0.0];
1317        }
1318        if (heading)
1319            [paragraphStyle setHeaderLevel:heading];
1320        if (isParagraph) {
1321            // FIXME: Why are we ignoring margin-top?
1322            float marginLeft = 0.0;
1323            if (_caches->floatPropertyValueForNode(coreBlockElement, CSSPropertyMarginLeft, marginLeft) && marginLeft > 0.0)
1324                [paragraphStyle setHeadIndent:marginLeft];
1325            float textIndent = 0.0;
1326            if (_caches->floatPropertyValueForNode(coreBlockElement, CSSPropertyTextIndent, textIndent) && textIndent > 0.0)
1327                [paragraphStyle setFirstLineHeadIndent:[paragraphStyle headIndent] + textIndent];
1328            float marginRight = 0.0;
1329            if (_caches->floatPropertyValueForNode(coreBlockElement, CSSPropertyMarginRight, marginRight) && marginRight > 0.0)
1330                [paragraphStyle setTailIndent:-marginRight];
1331            float marginBottom = 0.0;
1332            if (_caches->floatPropertyValueForNode(coreBlockElement, CSSPropertyMarginRight, marginBottom) && marginBottom > 0.0)
1333                [paragraphStyle setParagraphSpacing:marginBottom];
1334        }
1335        if ([_textLists count] > 0)
1336            [paragraphStyle setTextLists:_textLists];
1337        if ([_textBlocks count] > 0)
1338            [paragraphStyle setTextBlocks:_textBlocks];
1339        [attrs setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
1340        [paragraphStyle release];
1341    }
1342    return attrs;
1343}
1344
1345
1346NSDictionary* HTMLConverter::attributesForElement(Element& element)
1347{
1348    auto& attributes = m_attributesForElements.add(&element, nullptr).iterator->value;
1349    if (!attributes)
1350        attributes = computedAttributesForElement(element);
1351    return attributes.get();
1352}
1353
1354NSDictionary* HTMLConverter::aggregatedAttributesForAncestors(CharacterData& node)
1355{
1356    Node* ancestor = node.parentNode();
1357    while (ancestor && !ancestor->isElementNode())
1358        ancestor = ancestor->parentNode();
1359    if (!ancestor)
1360        return nullptr;
1361    return aggregatedAttributesForElementAndItsAncestors(*toElement(ancestor));
1362}
1363
1364NSDictionary* HTMLConverter::aggregatedAttributesForElementAndItsAncestors(Element& element)
1365{
1366    auto& cachedAttributes = m_aggregatedAttributesForElements.add(&element, nullptr).iterator->value;
1367    if (cachedAttributes)
1368        return cachedAttributes.get();
1369
1370    NSDictionary* attributesForCurrentElement = attributesForElement(element);
1371    ASSERT(attributesForCurrentElement);
1372
1373    Node* ancestor = element.parentNode();
1374    while (ancestor && !ancestor->isElementNode())
1375        ancestor = ancestor->parentNode();
1376
1377    if (!ancestor) {
1378        cachedAttributes = attributesForCurrentElement;
1379        return attributesForCurrentElement;
1380    }
1381
1382    RetainPtr<NSMutableDictionary> attributesForAncestors = adoptNS([aggregatedAttributesForElementAndItsAncestors(*toElement(ancestor)) mutableCopy]);
1383    [attributesForAncestors addEntriesFromDictionary:attributesForCurrentElement];
1384    m_aggregatedAttributesForElements.set(&element, attributesForAncestors);
1385
1386    return attributesForAncestors.get();
1387}
1388
1389void HTMLConverter::_newParagraphForElement(Element& element, NSString *tag, BOOL flag, BOOL suppressTrailingSpace)
1390{
1391    NSUInteger textLength = [_attrStr length];
1392    unichar lastChar = (textLength > 0) ? [[_attrStr string] characterAtIndex:textLength - 1] : '\n';
1393    NSRange rangeToReplace = (suppressTrailingSpace && _flags.isSoft && (lastChar == ' ' || lastChar == NSLineSeparatorCharacter)) ? NSMakeRange(textLength - 1, 1) : NSMakeRange(textLength, 0);
1394    BOOL needBreak = (flag || lastChar != '\n');
1395    if (needBreak) {
1396        NSString *string = (([@"BODY" isEqualToString:tag] || [@"HTML" isEqualToString:tag]) ? @"" : @"\n");
1397        [_writingDirectionArray removeAllObjects];
1398        [_attrStr replaceCharactersInRange:rangeToReplace withString:string];
1399        if (rangeToReplace.location < _domRangeStartIndex)
1400            _domRangeStartIndex += [string length] - rangeToReplace.length;
1401        rangeToReplace.length = [string length];
1402        NSDictionary *attrs = attributesForElement(element);
1403        if (rangeToReplace.length > 0)
1404            [_attrStr setAttributes:attrs range:rangeToReplace];
1405        _flags.isSoft = YES;
1406    }
1407}
1408
1409void HTMLConverter::_newLineForElement(Element& element)
1410{
1411    unichar c = NSLineSeparatorCharacter;
1412    RetainPtr<NSString> string = adoptNS([[NSString alloc] initWithCharacters:&c length:1]);
1413    NSUInteger textLength = [_attrStr length];
1414    NSRange rangeToReplace = NSMakeRange(textLength, 0);
1415    [_attrStr replaceCharactersInRange:rangeToReplace withString:string.get()];
1416    rangeToReplace.length = [string length];
1417    if (rangeToReplace.location < _domRangeStartIndex)
1418        _domRangeStartIndex += rangeToReplace.length;
1419    NSDictionary *attrs = attributesForElement(element);
1420    if (rangeToReplace.length > 0)
1421        [_attrStr setAttributes:attrs range:rangeToReplace];
1422    _flags.isSoft = YES;
1423}
1424
1425void HTMLConverter::_newTabForElement(Element& element)
1426{
1427    NSString *string = @"\t";
1428    NSUInteger textLength = [_attrStr length];
1429    unichar lastChar = (textLength > 0) ? [[_attrStr string] characterAtIndex:textLength - 1] : '\n';
1430    NSRange rangeToReplace = (_flags.isSoft && lastChar == ' ') ? NSMakeRange(textLength - 1, 1) : NSMakeRange(textLength, 0);
1431    [_attrStr replaceCharactersInRange:rangeToReplace withString:string];
1432    rangeToReplace.length = [string length];
1433    if (rangeToReplace.location < _domRangeStartIndex)
1434        _domRangeStartIndex += rangeToReplace.length;
1435    NSDictionary *attrs = attributesForElement(element);
1436    if (rangeToReplace.length > 0)
1437        [_attrStr setAttributes:attrs range:rangeToReplace];
1438    _flags.isSoft = YES;
1439}
1440
1441static Class _WebMessageDocumentClass()
1442{
1443    static Class _WebMessageDocumentClass = Nil;
1444    static BOOL lookedUpClass = NO;
1445    if (!lookedUpClass) {
1446        // If the class is not there, we don't want to try again
1447        _WebMessageDocumentClass = objc_lookUpClass("MFWebMessageDocument");
1448        if (_WebMessageDocumentClass && ![_WebMessageDocumentClass respondsToSelector:@selector(document:attachment:forURL:)])
1449            _WebMessageDocumentClass = Nil;
1450        lookedUpClass = YES;
1451    }
1452    return _WebMessageDocumentClass;
1453}
1454
1455BOOL HTMLConverter::_addAttachmentForElement(Element& element, NSURL *url, BOOL needsParagraph, BOOL usePlaceholder)
1456{
1457    BOOL retval = NO;
1458    BOOL notFound = NO;
1459    NSFileWrapper *fileWrapper = nil;
1460    Frame* frame = element.document().frame();
1461    DocumentLoader *dataSource = frame->loader().frameHasLoaded() ? frame->loader().documentLoader() : 0;
1462    BOOL ignoreOrientation = YES;
1463
1464    if ([url isFileURL]) {
1465        NSString *path = [[url path] stringByStandardizingPath];
1466        if (path)
1467            fileWrapper = [[[NSFileWrapper alloc] initWithURL:url options:0 error:NULL] autorelease];
1468    }
1469    if (!fileWrapper) {
1470        RefPtr<ArchiveResource> resource = dataSource->subresource(url);
1471        if (!resource)
1472            resource = dataSource->subresource(url);
1473
1474        const String& mimeType = resource->mimeType();
1475        if (usePlaceholder && resource && mimeType == "text/html")
1476            notFound = YES;
1477        if (resource && !notFound) {
1478            fileWrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:resource->data()->createNSData().get()] autorelease];
1479            [fileWrapper setPreferredFilename:suggestedFilenameWithMIMEType(url, mimeType)];
1480        }
1481    }
1482#if !PLATFORM(IOS)
1483    if (!fileWrapper && !notFound) {
1484        fileWrapper = fileWrapperForURL(dataSource, url);
1485        if (usePlaceholder && fileWrapper && [[[[fileWrapper preferredFilename] pathExtension] lowercaseString] hasPrefix:@"htm"])
1486            notFound = YES;
1487        if (notFound)
1488            fileWrapper = nil;
1489    }
1490    if (!fileWrapper && !notFound) {
1491        fileWrapper = fileWrapperForURL(m_dataSource, url);
1492        if (usePlaceholder && fileWrapper && [[[[fileWrapper preferredFilename] pathExtension] lowercaseString] hasPrefix:@"htm"])
1493            notFound = YES;
1494        if (notFound)
1495            fileWrapper = nil;
1496    }
1497#endif
1498    if (!fileWrapper && !notFound && url) {
1499        // Special handling for Mail attachments, until WebKit provides a standard way to get the data.
1500        Class WebMessageDocumentClass = _WebMessageDocumentClass();
1501        if (WebMessageDocumentClass) {
1502            NSTextAttachment *mimeTextAttachment = nil;
1503            [WebMessageDocumentClass document:NULL attachment:&mimeTextAttachment forURL:url];
1504            if (mimeTextAttachment && [mimeTextAttachment respondsToSelector:@selector(fileWrapper)]) {
1505                fileWrapper = [mimeTextAttachment performSelector:@selector(fileWrapper)];
1506                ignoreOrientation = NO;
1507            }
1508        }
1509    }
1510    if (fileWrapper || usePlaceholder) {
1511        NSUInteger textLength = [_attrStr length];
1512        RetainPtr<NSTextAttachment> attachment = adoptNS([[PlatformNSTextAttachment alloc] initWithFileWrapper:fileWrapper]);
1513#if PLATFORM(IOS)
1514        float verticalAlign = 0.0;
1515        _caches->floatPropertyValueForNode(element, CSSPropertyVerticalAlign, verticalAlign);
1516        attachment.get().bounds = CGRectMake(0, (verticalAlign / 100) * element.clientHeight(), element.clientWidth(), element.clientHeight());
1517#endif
1518        RetainPtr<NSString> string = adoptNS([[NSString alloc] initWithFormat:(needsParagraph ? @"%C\n" : @"%C"), static_cast<unichar>(NSAttachmentCharacter)]);
1519        NSRange rangeToReplace = NSMakeRange(textLength, 0);
1520        NSDictionary *attrs;
1521        if (fileWrapper) {
1522#if !PLATFORM(IOS)
1523            if (ignoreOrientation)
1524                [attachment setIgnoresOrientation:YES];
1525#endif
1526        } else {
1527#if PLATFORM(IOS)
1528            [attachment release];
1529            NSURL *missingImageURL = [_webKitBundle() URLForResource:@"missing_image" withExtension:@"tiff"];
1530            ASSERT_WITH_MESSAGE(missingImageURL != nil, "Unable to find missing_image.tiff!");
1531            NSFileWrapper *missingImageFileWrapper = [[[NSFileWrapper alloc] initWithURL:missingImageURL options:0 error:NULL] autorelease];
1532            attachment = [[PlatformNSTextAttachment alloc] initWithFileWrapper:missingImageFileWrapper];
1533#else
1534            static NSImage *missingImage = nil;
1535            NSTextAttachmentCell *cell;
1536            cell = [[NSTextAttachmentCell alloc] initImageCell:missingImage];
1537            [attachment setAttachmentCell:cell];
1538            [cell release];
1539#endif
1540        }
1541        [_attrStr replaceCharactersInRange:rangeToReplace withString:string.get()];
1542        rangeToReplace.length = [string length];
1543        if (rangeToReplace.location < _domRangeStartIndex)
1544            _domRangeStartIndex += rangeToReplace.length;
1545        attrs = attributesForElement(element);
1546        if (rangeToReplace.length > 0) {
1547            [_attrStr setAttributes:attrs range:rangeToReplace];
1548            rangeToReplace.length = 1;
1549            [_attrStr addAttribute:NSAttachmentAttributeName value:attachment.get() range:rangeToReplace];
1550        }
1551        _flags.isSoft = NO;
1552        retval = YES;
1553    }
1554    return retval;
1555}
1556
1557void HTMLConverter::_addQuoteForElement(Element& element, BOOL opening, NSInteger level)
1558{
1559    unichar c = ((level % 2) == 0) ? (opening ? 0x201c : 0x201d) : (opening ? 0x2018 : 0x2019);
1560    RetainPtr<NSString> string = adoptNS([[NSString alloc] initWithCharacters:&c length:1]);
1561    NSUInteger textLength = [_attrStr length];
1562    NSRange rangeToReplace = NSMakeRange(textLength, 0);
1563    [_attrStr replaceCharactersInRange:rangeToReplace withString:string.get()];
1564    rangeToReplace.length = [string length];
1565    if (rangeToReplace.location < _domRangeStartIndex)
1566        _domRangeStartIndex += rangeToReplace.length;
1567    RetainPtr<NSDictionary> attrs = attributesForElement(element);
1568    if (rangeToReplace.length > 0)
1569        [_attrStr setAttributes:attrs.get() range:rangeToReplace];
1570    _flags.isSoft = NO;
1571}
1572
1573void HTMLConverter::_addValue(NSString *value, Element& element)
1574{
1575    NSUInteger textLength = [_attrStr length];
1576    NSUInteger valueLength = [value length];
1577    NSRange rangeToReplace = NSMakeRange(textLength, 0);
1578    if (valueLength) {
1579        [_attrStr replaceCharactersInRange:rangeToReplace withString:value];
1580        rangeToReplace.length = valueLength;
1581        if (rangeToReplace.location < _domRangeStartIndex)
1582            _domRangeStartIndex += rangeToReplace.length;
1583        RetainPtr<NSDictionary> attrs = attributesForElement(element);
1584        if (rangeToReplace.length > 0)
1585            [_attrStr setAttributes:attrs.get() range:rangeToReplace];
1586        _flags.isSoft = NO;
1587    }
1588}
1589
1590void HTMLConverter::_fillInBlock(NSTextBlock *block, Element& element, PlatformColor *backgroundColor, CGFloat extraMargin, CGFloat extraPadding, BOOL isTable)
1591{
1592    float result = 0;
1593
1594    NSString *width = element.getAttribute(widthAttr);
1595    if ((width && [width length]) || !isTable) {
1596        if (_caches->floatPropertyValueForNode(element, CSSPropertyWidth, result))
1597            [block setValue:result type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockWidth];
1598    }
1599
1600    if (_caches->floatPropertyValueForNode(element, CSSPropertyMinWidth, result))
1601        [block setValue:result type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockMinimumWidth];
1602    if (_caches->floatPropertyValueForNode(element, CSSPropertyMaxWidth, result))
1603        [block setValue:result type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockMaximumWidth];
1604    if (_caches->floatPropertyValueForNode(element, CSSPropertyMinHeight, result))
1605        [block setValue:result type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockMinimumHeight];
1606    if (_caches->floatPropertyValueForNode(element, CSSPropertyMaxHeight, result))
1607        [block setValue:result type:NSTextBlockAbsoluteValueType forDimension:NSTextBlockMaximumHeight];
1608
1609    if (_caches->floatPropertyValueForNode(element, CSSPropertyPaddingLeft, result))
1610        [block setWidth:result + extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinXEdge];
1611    else
1612        [block setWidth:extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinXEdge];
1613
1614    if (_caches->floatPropertyValueForNode(element, CSSPropertyPaddingTop, result))
1615        [block setWidth:result + extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinYEdge];
1616    else
1617        [block setWidth:extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMinYEdge];
1618
1619    if (_caches->floatPropertyValueForNode(element, CSSPropertyPaddingRight, result))
1620        [block setWidth:result + extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxXEdge];
1621    else
1622        [block setWidth:extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxXEdge];
1623
1624    if (_caches->floatPropertyValueForNode(element, CSSPropertyPaddingBottom, result))
1625        [block setWidth:result + extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxYEdge];
1626    else
1627        [block setWidth:extraPadding type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockPadding edge:NSMaxYEdge];
1628
1629    if (_caches->floatPropertyValueForNode(element, CSSPropertyBorderLeftWidth, result))
1630        [block setWidth:result type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder edge:NSMinXEdge];
1631    if (_caches->floatPropertyValueForNode(element, CSSPropertyBorderTopWidth, result))
1632        [block setWidth:result type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder edge:NSMinYEdge];
1633    if (_caches->floatPropertyValueForNode(element, CSSPropertyBorderRightWidth, result))
1634        [block setWidth:result type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder edge:NSMaxXEdge];
1635    if (_caches->floatPropertyValueForNode(element, CSSPropertyBorderBottomWidth, result))
1636        [block setWidth:result type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockBorder edge:NSMaxYEdge];
1637
1638    if (_caches->floatPropertyValueForNode(element, CSSPropertyMarginLeft, result))
1639        [block setWidth:result + extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMinXEdge];
1640    else
1641        [block setWidth:extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMinXEdge];
1642    if (_caches->floatPropertyValueForNode(element, CSSPropertyMarginTop, result))
1643        [block setWidth:result + extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMinYEdge];
1644    else
1645        [block setWidth:extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMinYEdge];
1646    if (_caches->floatPropertyValueForNode(element, CSSPropertyMarginRight, result))
1647        [block setWidth:result + extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMaxXEdge];
1648    else
1649        [block setWidth:extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMaxXEdge];
1650    if (_caches->floatPropertyValueForNode(element, CSSPropertyMarginBottom, result))
1651        [block setWidth:result + extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMaxYEdge];
1652    else
1653        [block setWidth:extraMargin type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSMaxYEdge];
1654
1655    PlatformColor *color = nil;
1656    if ((color = _colorForElement(element, CSSPropertyBackgroundColor)))
1657        [block setBackgroundColor:color];
1658    if (!color && backgroundColor)
1659        [block setBackgroundColor:backgroundColor];
1660
1661    if ((color = _colorForElement(element, CSSPropertyBorderLeftColor)))
1662        [block setBorderColor:color forEdge:NSMinXEdge];
1663
1664    if ((color = _colorForElement(element, CSSPropertyBorderTopColor)))
1665        [block setBorderColor:color forEdge:NSMinYEdge];
1666    if ((color = _colorForElement(element, CSSPropertyBorderRightColor)))
1667        [block setBorderColor:color forEdge:NSMaxXEdge];
1668    if ((color = _colorForElement(element, CSSPropertyBorderBottomColor)))
1669        [block setBorderColor:color forEdge:NSMaxYEdge];
1670}
1671
1672static inline BOOL read2DigitNumber(const char **pp, int8_t *outval)
1673{
1674    BOOL result = NO;
1675    char c1 = *(*pp)++, c2;
1676    if (isASCIIDigit(c1)) {
1677        c2 = *(*pp)++;
1678        if (isASCIIDigit(c2)) {
1679            *outval = 10 * (c1 - '0') + (c2 - '0');
1680            result = YES;
1681        }
1682    }
1683    return result;
1684}
1685
1686static inline NSDate *_dateForString(NSString *string)
1687{
1688    const char *p = [string UTF8String];
1689    RetainPtr<NSDateComponents> dateComponents = adoptNS([[NSDateComponents alloc] init]);
1690
1691    // Set the time zone to GMT
1692    [dateComponents setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
1693
1694    NSInteger year = 0;
1695    while (*p && isASCIIDigit(*p))
1696        year = 10 * year + *p++ - '0';
1697    if (*p++ != '-')
1698        return nil;
1699    [dateComponents setYear:year];
1700
1701    int8_t component;
1702    if (!read2DigitNumber(&p, &component) || *p++ != '-')
1703        return nil;
1704    [dateComponents setMonth:component];
1705
1706    if (!read2DigitNumber(&p, &component) || *p++ != 'T')
1707        return nil;
1708    [dateComponents setDay:component];
1709
1710    if (!read2DigitNumber(&p, &component) || *p++ != ':')
1711        return nil;
1712    [dateComponents setHour:component];
1713
1714    if (!read2DigitNumber(&p, &component) || *p++ != ':')
1715        return nil;
1716    [dateComponents setMinute:component];
1717
1718    if (!read2DigitNumber(&p, &component) || *p++ != 'Z')
1719        return nil;
1720    [dateComponents setSecond:component];
1721
1722#if (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090)
1723    NSString *calendarIdentifier = NSCalendarIdentifierGregorian;
1724#else
1725    NSString *calendarIdentifier = NSGregorianCalendar;
1726#endif
1727
1728    return [[[[NSCalendar alloc] initWithCalendarIdentifier:calendarIdentifier] autorelease] dateFromComponents:dateComponents.get()];
1729}
1730
1731static NSInteger _colCompare(id block1, id block2, void *)
1732{
1733    NSInteger col1 = [(NSTextTableBlock *)block1 startingColumn];
1734    NSInteger col2 = [(NSTextTableBlock *)block2 startingColumn];
1735    return ((col1 < col2) ? NSOrderedAscending : ((col1 == col2) ? NSOrderedSame : NSOrderedDescending));
1736}
1737
1738void HTMLConverter::_processMetaElementWithName(NSString *name, NSString *content)
1739{
1740    NSString *key = nil;
1741    if (NSOrderedSame == [@"CocoaVersion" compare:name options:NSCaseInsensitiveSearch]) {
1742        CGFloat versionNumber = [content doubleValue];
1743        if (versionNumber > 0.0) {
1744            // ??? this should be keyed off of version number in future
1745            [_documentAttrs removeObjectForKey:NSConvertedDocumentAttribute];
1746            [_documentAttrs setObject:[NSNumber numberWithDouble:versionNumber] forKey:NSCocoaVersionDocumentAttribute];
1747        }
1748#if PLATFORM(IOS)
1749    } else if (NSOrderedSame == [@"Generator" compare:name options:NSCaseInsensitiveSearch]) {
1750        key = NSGeneratorDocumentAttribute;
1751#endif
1752    } else if (NSOrderedSame == [@"Keywords" compare:name options:NSCaseInsensitiveSearch]) {
1753        if (content && [content length] > 0) {
1754            NSArray *array;
1755            // ??? need better handling here and throughout
1756            if ([content rangeOfString:@", "].length == 0 && [content rangeOfString:@","].length > 0)
1757                array = [content componentsSeparatedByString:@","];
1758            else if ([content rangeOfString:@", "].length == 0 && [content rangeOfString:@" "].length > 0)
1759                array = [content componentsSeparatedByString:@" "];
1760            else
1761                array = [content componentsSeparatedByString:@", "];
1762            [_documentAttrs setObject:array forKey:NSKeywordsDocumentAttribute];
1763        }
1764    } else if (NSOrderedSame == [@"Author" compare:name options:NSCaseInsensitiveSearch])
1765        key = NSAuthorDocumentAttribute;
1766    else if (NSOrderedSame == [@"LastAuthor" compare:name options:NSCaseInsensitiveSearch])
1767        key = NSEditorDocumentAttribute;
1768    else if (NSOrderedSame == [@"Company" compare:name options:NSCaseInsensitiveSearch])
1769        key = NSCompanyDocumentAttribute;
1770    else if (NSOrderedSame == [@"Copyright" compare:name options:NSCaseInsensitiveSearch])
1771        key = NSCopyrightDocumentAttribute;
1772    else if (NSOrderedSame == [@"Subject" compare:name options:NSCaseInsensitiveSearch])
1773        key = NSSubjectDocumentAttribute;
1774    else if (NSOrderedSame == [@"Description" compare:name options:NSCaseInsensitiveSearch] || NSOrderedSame == [@"Comment" compare:name options:NSCaseInsensitiveSearch])
1775        key = NSCommentDocumentAttribute;
1776    else if (NSOrderedSame == [@"CreationTime" compare:name options:NSCaseInsensitiveSearch]) {
1777        if (content && [content length] > 0) {
1778            NSDate *date = _dateForString(content);
1779            if (date)
1780                [_documentAttrs setObject:date forKey:NSCreationTimeDocumentAttribute];
1781        }
1782    } else if (NSOrderedSame == [@"ModificationTime" compare:name options:NSCaseInsensitiveSearch]) {
1783        if (content && [content length] > 0) {
1784            NSDate *date = _dateForString(content);
1785            if (date)
1786                [_documentAttrs setObject:date forKey:NSModificationTimeDocumentAttribute];
1787        }
1788    }
1789#if PLATFORM(IOS)
1790    else if (NSOrderedSame == [@"DisplayName" compare:name options:NSCaseInsensitiveSearch] || NSOrderedSame == [@"IndexTitle" compare:name options:NSCaseInsensitiveSearch])
1791        key = NSDisplayNameDocumentAttribute;
1792    else if (NSOrderedSame == [@"robots" compare:name options:NSCaseInsensitiveSearch]) {
1793        if ([content rangeOfString:@"noindex" options:NSCaseInsensitiveSearch].length > 0)
1794            [_documentAttrs setObject:[NSNumber numberWithInteger:1] forKey:NSNoIndexDocumentAttribute];
1795    }
1796#endif
1797    if (key && content && [content length] > 0)
1798        [_documentAttrs setObject:content forKey:key];
1799}
1800
1801void HTMLConverter::_processHeadElement(Element& element)
1802{
1803    // FIXME: Should gather data from other sources e.g. Word, but for that we would need to be able to get comments from DOM
1804
1805    for (HTMLMetaElement* child = Traversal<HTMLMetaElement>::firstChild(&element); child; child = Traversal<HTMLMetaElement>::nextSibling(child)) {
1806        NSString *name = child->name();
1807        NSString *content = child->content();
1808        if (name && content)
1809            _processMetaElementWithName(name, content);
1810    }
1811}
1812
1813BOOL HTMLConverter::_enterElement(Element& element, BOOL embedded)
1814{
1815    String displayValue = _caches->propertyValueForNode(element, CSSPropertyDisplay);
1816
1817    if (element.hasTagName(headTag) && !embedded)
1818        _processHeadElement(element);
1819    else if (!displayValue.length() || !(displayValue == "none" || displayValue == "table-column" || displayValue == "table-column-group")) {
1820        if (_caches->isBlockElement(element) && !element.hasTagName(brTag) && !(displayValue == "table-cell" && [_textTables count] == 0)
1821            && !([_textLists count] > 0 && displayValue == "block" && !element.hasTagName(liTag) && !element.hasTagName(ulTag) && !element.hasTagName(olTag)))
1822            _newParagraphForElement(element, element.tagName(), NO, YES);
1823        return YES;
1824    }
1825    return NO;
1826}
1827
1828void HTMLConverter::_addTableForElement(Element *tableElement)
1829{
1830    RetainPtr<NSTextTable> table = adoptNS([[PlatformNSTextTable alloc] init]);
1831    CGFloat cellSpacingVal = 1;
1832    CGFloat cellPaddingVal = 1;
1833    [table setNumberOfColumns:1];
1834    [table setLayoutAlgorithm:NSTextTableAutomaticLayoutAlgorithm];
1835    [table setCollapsesBorders:NO];
1836    [table setHidesEmptyCells:NO];
1837
1838    if (tableElement) {
1839        ASSERT(tableElement);
1840        Element& coreTableElement = *tableElement;
1841
1842        NSString *cellSpacing = coreTableElement.getAttribute(cellspacingAttr);
1843        if (cellSpacing && [cellSpacing length] > 0 && ![cellSpacing hasSuffix:@"%"])
1844            cellSpacingVal = [cellSpacing floatValue];
1845        NSString *cellPadding = coreTableElement.getAttribute(cellpaddingAttr);
1846        if (cellPadding && [cellPadding length] > 0 && ![cellPadding hasSuffix:@"%"])
1847            cellPaddingVal = [cellPadding floatValue];
1848
1849        _fillInBlock(table.get(), coreTableElement, nil, 0, 0, YES);
1850
1851        if (_caches->propertyValueForNode(coreTableElement, CSSPropertyBorderCollapse) == "collapse") {
1852            [table setCollapsesBorders:YES];
1853            cellSpacingVal = 0;
1854        }
1855        if (_caches->propertyValueForNode(coreTableElement, CSSPropertyEmptyCells) == "hide")
1856            [table setHidesEmptyCells:YES];
1857        if (_caches->propertyValueForNode(coreTableElement, CSSPropertyTableLayout) == "fixed")
1858            [table setLayoutAlgorithm:NSTextTableFixedLayoutAlgorithm];
1859    }
1860
1861    [_textTables addObject:table.get()];
1862    [_textTableSpacings addObject:[NSNumber numberWithDouble:cellSpacingVal]];
1863    [_textTablePaddings addObject:[NSNumber numberWithDouble:cellPaddingVal]];
1864    [_textTableRows addObject:[NSNumber numberWithInteger:0]];
1865    [_textTableRowArrays addObject:[NSMutableArray array]];
1866}
1867
1868void HTMLConverter::_addTableCellForElement(Element* element)
1869{
1870    NSTextTable *table = [_textTables lastObject];
1871    NSInteger rowNumber = [[_textTableRows lastObject] integerValue];
1872    NSInteger columnNumber = 0;
1873    NSInteger rowSpan = 1;
1874    NSInteger colSpan = 1;
1875    NSMutableArray *rowArray = [_textTableRowArrays lastObject];
1876    NSUInteger count = [rowArray count];
1877    PlatformColor *color = ([_textTableRowBackgroundColors count] > 0) ? [_textTableRowBackgroundColors lastObject] : nil;
1878    NSTextTableBlock *previousBlock;
1879    CGFloat cellSpacingVal = [[_textTableSpacings lastObject] floatValue];
1880    if ([color isEqual:[PlatformColorClass clearColor]]) color = nil;
1881    for (NSUInteger i = 0; i < count; i++) {
1882        previousBlock = [rowArray objectAtIndex:i];
1883        if (columnNumber >= [previousBlock startingColumn] && columnNumber < [previousBlock startingColumn] + [previousBlock columnSpan])
1884            columnNumber = [previousBlock startingColumn] + [previousBlock columnSpan];
1885    }
1886
1887    RetainPtr<NSTextTableBlock> block;
1888
1889    if (element) {
1890        if (isHTMLTableCellElement(*element)) {
1891            HTMLTableCellElement& tableCellElement = toHTMLTableCellElement(*element);
1892
1893            rowSpan = tableCellElement.rowSpan();
1894            if (rowSpan < 1)
1895                rowSpan = 1;
1896            colSpan = tableCellElement.colSpan();
1897            if (colSpan < 1)
1898                colSpan = 1;
1899        }
1900
1901        block = adoptNS([[PlatformNSTextTableBlock alloc] initWithTable:table startingRow:rowNumber rowSpan:rowSpan startingColumn:columnNumber columnSpan:colSpan]);
1902
1903        String verticalAlign = _caches->propertyValueForNode(*element, CSSPropertyVerticalAlign);
1904
1905        _fillInBlock(block.get(), *element, color, cellSpacingVal / 2, 0, NO);
1906        if (verticalAlign == "middle")
1907            [block setVerticalAlignment:NSTextBlockMiddleAlignment];
1908        else if (verticalAlign == "bottom")
1909            [block setVerticalAlignment:NSTextBlockBottomAlignment];
1910        else if (verticalAlign == "baseline")
1911            [block setVerticalAlignment:NSTextBlockBaselineAlignment];
1912        else if (verticalAlign == "top")
1913            [block setVerticalAlignment:NSTextBlockTopAlignment];
1914    } else {
1915        block = adoptNS([[PlatformNSTextTableBlock alloc] initWithTable:table startingRow:rowNumber rowSpan:rowSpan startingColumn:columnNumber columnSpan:colSpan]);
1916    }
1917
1918    [_textBlocks addObject:block.get()];
1919    [rowArray addObject:block.get()];
1920    [rowArray sortUsingFunction:_colCompare context:NULL];
1921}
1922
1923BOOL HTMLConverter::_processElement(Element& element, NSInteger depth)
1924{
1925    BOOL retval = YES;
1926    BOOL isBlockLevel = _caches->isBlockElement(element);
1927    String displayValue = _caches->propertyValueForNode(element, CSSPropertyDisplay);
1928    if (isBlockLevel)
1929        [_writingDirectionArray removeAllObjects];
1930    else {
1931        String bidi = _caches->propertyValueForNode(element, CSSPropertyUnicodeBidi);
1932        if (bidi == "embed") {
1933            NSUInteger val = NSTextWritingDirectionEmbedding;
1934            if (_caches->propertyValueForNode(element, CSSPropertyDirection) == "rtl")
1935                val |= NSWritingDirectionRightToLeft;
1936            [_writingDirectionArray addObject:[NSNumber numberWithUnsignedInteger:val]];
1937        } else if (bidi == "bidi-override") {
1938            NSUInteger val = NSTextWritingDirectionOverride;
1939            if (_caches->propertyValueForNode(element, CSSPropertyDirection) == "rtl")
1940                val |= NSWritingDirectionRightToLeft;
1941            [_writingDirectionArray addObject:[NSNumber numberWithUnsignedInteger:val]];
1942        }
1943    }
1944    if (displayValue == "table" || ([_textTables count] == 0 && displayValue == "table-row-group")) {
1945        Element* tableElement = &element;
1946        if (displayValue == "table-row-group") {
1947            // If we are starting in medias res, the first thing we see may be the tbody, so go up to the table
1948            tableElement = _blockLevelElementForNode(element.parentNode());
1949            if (!tableElement || _caches->propertyValueForNode(*tableElement, CSSPropertyDisplay) != "table")
1950                tableElement = &element;
1951        }
1952        while ([_textTables count] > [_textBlocks count])
1953            _addTableCellForElement(nil);
1954        _addTableForElement(tableElement);
1955    } else if (displayValue == "table-footer-group" && [_textTables count] > 0) {
1956        m_textTableFooters.add([_textTables lastObject], &element);
1957        retval = NO;
1958    } else if (displayValue == "table-row" && [_textTables count] > 0) {
1959        PlatformColor *color = _colorForElement(element, CSSPropertyBackgroundColor);
1960        if (!color)
1961            color = [PlatformColorClass clearColor];
1962        [_textTableRowBackgroundColors addObject:color];
1963    } else if (displayValue == "table-cell") {
1964        while ([_textTables count] < [_textBlocks count] + 1)
1965            _addTableForElement(nil);
1966        _addTableCellForElement(&element);
1967    } else if (element.hasTagName(imgTag)) {
1968        NSString *urlString = element.getAttribute(srcAttr);
1969        if (urlString && [urlString length] > 0) {
1970            NSURL *url = element.document().completeURL(stripLeadingAndTrailingHTMLSpaces(urlString));
1971            if (!url)
1972                url = [NSURL _web_URLWithString:[urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] relativeToURL:_baseURL];
1973#if PLATFORM(IOS)
1974            BOOL usePlaceholderImage = NO;
1975#else
1976            BOOL usePlaceholderImage = YES;
1977#endif
1978            if (url)
1979                _addAttachmentForElement(element, url, isBlockLevel, usePlaceholderImage);
1980        }
1981        retval = NO;
1982    } else if (element.hasTagName(objectTag)) {
1983        NSString *baseString = element.getAttribute(codebaseAttr);
1984        NSString *urlString = element.getAttribute(dataAttr);
1985        NSString *declareString = element.getAttribute(declareAttr);
1986        if (urlString && [urlString length] > 0 && ![@"true" isEqualToString:declareString]) {
1987            NSURL *baseURL = nil;
1988            NSURL *url = nil;
1989            if (baseString && [baseString length] > 0) {
1990                baseURL = element.document().completeURL(stripLeadingAndTrailingHTMLSpaces(baseString));
1991                if (!baseURL)
1992                    baseURL = [NSURL _web_URLWithString:[baseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] relativeToURL:_baseURL];
1993            }
1994            if (baseURL)
1995                url = [NSURL _web_URLWithString:[urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] relativeToURL:baseURL];
1996            if (!url)
1997                url = element.document().completeURL(stripLeadingAndTrailingHTMLSpaces(urlString));
1998            if (!url)
1999                url = [NSURL _web_URLWithString:[urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] relativeToURL:_baseURL];
2000            if (url)
2001                retval = !_addAttachmentForElement(element, url, isBlockLevel, NO);
2002        }
2003    } else if (element.hasTagName(frameTag) || element.hasTagName(iframeTag)) {
2004        if (Document* contentDocument = toHTMLFrameElementBase(element).contentDocument()) {
2005            _traverseNode(*contentDocument, depth + 1, true /* embedded */);
2006            retval = NO;
2007        }
2008    } else if (element.hasTagName(brTag)) {
2009        Element* blockElement = _blockLevelElementForNode(element.parentNode());
2010        NSString *breakClass = element.getAttribute(classAttr);
2011        NSString *blockTag = blockElement ? (NSString *)blockElement->tagName() : nil;
2012        BOOL isExtraBreak = [@"Apple-interchange-newline" isEqualToString:breakClass];
2013        BOOL blockElementIsParagraph = ([@"P" isEqualToString:blockTag] || [@"LI" isEqualToString:blockTag] || ([blockTag hasPrefix:@"H"] && 2 == [blockTag length]));
2014        if (isExtraBreak)
2015            _flags.hasTrailingNewline = YES;
2016        else {
2017            if (blockElement && blockElementIsParagraph)
2018                _newLineForElement(element);
2019            else
2020                _newParagraphForElement(element, element.tagName(), YES, NO);
2021        }
2022    } else if (element.hasTagName(ulTag)) {
2023        RetainPtr<NSTextList> list;
2024        String listStyleType = _caches->propertyValueForNode(element, CSSPropertyListStyleType);
2025        if (!listStyleType.length())
2026            listStyleType = @"disc";
2027        list = adoptNS([[PlatformNSTextList alloc] initWithMarkerFormat:String("{" + listStyleType + "}") options:0]);
2028        [_textLists addObject:list.get()];
2029    } else if (element.hasTagName(olTag)) {
2030        RetainPtr<NSTextList> list;
2031        String listStyleType = _caches->propertyValueForNode(element, CSSPropertyListStyleType);
2032        if (!listStyleType.length())
2033            listStyleType = "decimal";
2034        list = adoptNS([[PlatformNSTextList alloc] initWithMarkerFormat:String("{" + listStyleType + "}") options:0]);
2035        if (isHTMLOListElement(element)) {
2036            NSInteger startingItemNumber = toHTMLOListElement(element).start();;
2037            [list setStartingItemNumber:startingItemNumber];
2038        }
2039        [_textLists addObject:list.get()];
2040    } else if (element.hasTagName(qTag)) {
2041        _addQuoteForElement(element, YES, _quoteLevel++);
2042    } else if (element.hasTagName(inputTag)) {
2043        if (isHTMLInputElement(element)) {
2044            HTMLInputElement& inputElement = toHTMLInputElement(element);
2045            if (inputElement.type() == "text") {
2046                NSString *value = inputElement.value();
2047                if (value && [value length] > 0)
2048                    _addValue(value, element);
2049            }
2050        }
2051    } else if (element.hasTagName(textareaTag)) {
2052        if (isHTMLTextAreaElement(element)) {
2053            HTMLTextAreaElement& textAreaElement = toHTMLTextAreaElement(element);
2054            NSString *value = textAreaElement.value();
2055            if (value && [value length] > 0)
2056                _addValue(value, element);
2057        }
2058        retval = NO;
2059    }
2060    return retval;
2061}
2062
2063void HTMLConverter::_addMarkersToList(NSTextList *list, NSRange range)
2064{
2065    NSInteger itemNum = [list startingItemNumber];
2066    NSString *string = [_attrStr string];
2067    NSString *stringToInsert;
2068    NSDictionary *attrsToInsert = nil;
2069    PlatformFont *font;
2070    NSParagraphStyle *paragraphStyle;
2071    NSMutableParagraphStyle *newStyle;
2072    NSTextTab *tab = nil;
2073    NSTextTab *tabToRemove;
2074    NSRange paragraphRange;
2075    NSRange styleRange;
2076    NSUInteger textLength = [_attrStr length];
2077    NSUInteger listIndex;
2078    NSUInteger insertLength;
2079    NSUInteger i;
2080    NSUInteger count;
2081    NSArray *textLists;
2082    CGFloat markerLocation;
2083    CGFloat listLocation;
2084    CGFloat pointSize;
2085
2086    if (range.length == 0 || range.location >= textLength)
2087        return;
2088    if (NSMaxRange(range) > textLength)
2089        range.length = textLength - range.location;
2090    paragraphStyle = [_attrStr attribute:NSParagraphStyleAttributeName atIndex:range.location effectiveRange:NULL];
2091    if (paragraphStyle) {
2092        textLists = [paragraphStyle textLists];
2093        listIndex = [textLists indexOfObject:list];
2094        if (textLists && listIndex != NSNotFound) {
2095            for (NSUInteger idx = range.location; idx < NSMaxRange(range);) {
2096                paragraphRange = [string paragraphRangeForRange:NSMakeRange(idx, 0)];
2097                paragraphStyle = [_attrStr attribute:NSParagraphStyleAttributeName atIndex:idx effectiveRange:&styleRange];
2098                font = [_attrStr attribute:NSFontAttributeName atIndex:idx effectiveRange:NULL];
2099                pointSize = font ? [font pointSize] : 12;
2100                if ([[paragraphStyle textLists] count] == listIndex + 1) {
2101                    stringToInsert = [NSString stringWithFormat:@"\t%@\t", [list markerForItemNumber:itemNum++]];
2102                    insertLength = [stringToInsert length];
2103                    attrsToInsert = [PlatformNSTextList _standardMarkerAttributesForAttributes:[_attrStr attributesAtIndex:paragraphRange.location effectiveRange:NULL]];
2104
2105                    [_attrStr replaceCharactersInRange:NSMakeRange(paragraphRange.location, 0) withString:stringToInsert];
2106                    [_attrStr setAttributes:attrsToInsert range:NSMakeRange(paragraphRange.location, insertLength)];
2107                    range.length += insertLength;
2108                    paragraphRange.length += insertLength;
2109                    if (paragraphRange.location < _domRangeStartIndex)
2110                        _domRangeStartIndex += insertLength;
2111
2112                    newStyle = [paragraphStyle mutableCopy];
2113                    listLocation = (listIndex + 1) * 36;
2114                    markerLocation = listLocation - 25;
2115                    [newStyle setFirstLineHeadIndent:0];
2116                    [newStyle setHeadIndent:listLocation];
2117                    while ((count = [[newStyle tabStops] count]) > 0) {
2118                        for (i = 0, tabToRemove = nil; !tabToRemove && i < count; i++) {
2119                            tab = [[newStyle tabStops] objectAtIndex:i];
2120                            if ([tab location] <= listLocation)
2121                                tabToRemove = tab;
2122                        }
2123                        if (tabToRemove)
2124                            [newStyle removeTabStop:tab];
2125                        else
2126                            break;
2127                    }
2128                    tab = [[PlatformNSTextTab alloc] initWithType:NSLeftTabStopType location:markerLocation];
2129                    [newStyle addTabStop:tab];
2130                    [tab release];
2131#if PLATFORM(IOS)
2132                    tab = [[PlatformNSTextTab alloc] initWithTextAlignment:NSTextAlignmentNatural location:listLocation options:nil];
2133#else
2134                    tab = [[PlatformNSTextTab alloc] initWithTextAlignment:NSNaturalTextAlignment location:listLocation options:nil];
2135#endif
2136                    [newStyle addTabStop:tab];
2137                    [tab release];
2138                    [_attrStr addAttribute:NSParagraphStyleAttributeName value:newStyle range:paragraphRange];
2139                    [newStyle release];
2140
2141                    idx = NSMaxRange(paragraphRange);
2142                } else {
2143                    // skip any deeper-nested lists
2144                    idx = NSMaxRange(styleRange);
2145                }
2146            }
2147        }
2148    }
2149}
2150
2151void HTMLConverter::_exitElement(Element& element, NSInteger depth, NSUInteger startIndex)
2152{
2153    String displayValue = _caches->propertyValueForNode(element, CSSPropertyDisplay);
2154    NSRange range = NSMakeRange(startIndex, [_attrStr length] - startIndex);
2155    if (range.length > 0 && element.hasTagName(aTag)) {
2156        NSString *urlString = element.getAttribute(hrefAttr);
2157        NSString *strippedString = [urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
2158        if (urlString && [urlString length] > 0 && strippedString && [strippedString length] > 0 && ![strippedString hasPrefix:@"#"]) {
2159            NSURL *url = element.document().completeURL(stripLeadingAndTrailingHTMLSpaces(urlString));
2160            if (!url)
2161                url = element.document().completeURL(stripLeadingAndTrailingHTMLSpaces(strippedString));
2162            if (!url)
2163                url = [NSURL _web_URLWithString:strippedString relativeToURL:_baseURL];
2164            [_attrStr addAttribute:NSLinkAttributeName value:url ? (id)url : (id)urlString range:range];
2165        }
2166    }
2167    if (!_flags.reachedEnd && _caches->isBlockElement(element)) {
2168        [_writingDirectionArray removeAllObjects];
2169        if (displayValue == "table-cell" && [_textBlocks count] == 0) {
2170            _newTabForElement(element);
2171        } else if ([_textLists count] > 0 && displayValue == "block" && !element.hasTagName(liTag) && !element.hasTagName(ulTag) && !element.hasTagName(olTag)) {
2172            _newLineForElement(element);
2173        } else {
2174            _newParagraphForElement(element, element.tagName(), (range.length == 0), YES);
2175        }
2176    } else if ([_writingDirectionArray count] > 0) {
2177        String bidi = _caches->propertyValueForNode(element, CSSPropertyUnicodeBidi);
2178        if (bidi == "embed" || bidi == "bidi-override")
2179            [_writingDirectionArray removeLastObject];
2180    }
2181    range = NSMakeRange(startIndex, [_attrStr length] - startIndex);
2182    if (displayValue == "table" && [_textTables count] > 0) {
2183        NSTextTable *key = [_textTables lastObject];
2184        Element* footer = m_textTableFooters.get(key);
2185        while ([_textTables count] < [_textBlocks count] + 1)
2186            [_textBlocks removeLastObject];
2187        if (footer) {
2188            _traverseFooterNode(*footer, depth + 1);
2189            m_textTableFooters.remove(key);
2190        }
2191        [_textTables removeLastObject];
2192        [_textTableSpacings removeLastObject];
2193        [_textTablePaddings removeLastObject];
2194        [_textTableRows removeLastObject];
2195        [_textTableRowArrays removeLastObject];
2196    } else if (displayValue == "table-row" && [_textTables count] > 0) {
2197        NSTextTable *table = [_textTables lastObject];
2198        NSTextTableBlock *block;
2199        NSMutableArray *rowArray = [_textTableRowArrays lastObject], *previousRowArray;
2200        NSUInteger i, count;
2201        NSInteger numberOfColumns = [table numberOfColumns];
2202        NSInteger openColumn;
2203        NSInteger rowNumber = [[_textTableRows lastObject] integerValue];
2204        do {
2205            rowNumber++;
2206            previousRowArray = rowArray;
2207            rowArray = [NSMutableArray array];
2208            count = [previousRowArray count];
2209            for (i = 0; i < count; i++) {
2210                block = [previousRowArray objectAtIndex:i];
2211                if ([block startingColumn] + [block columnSpan] > numberOfColumns) numberOfColumns = [block startingColumn] + [block columnSpan];
2212                if ([block startingRow] + [block rowSpan] > rowNumber) [rowArray addObject:block];
2213            }
2214            count = [rowArray count];
2215            openColumn = 0;
2216            for (i = 0; i < count; i++) {
2217                block = [rowArray objectAtIndex:i];
2218                if (openColumn >= [block startingColumn] && openColumn < [block startingColumn] + [block columnSpan]) openColumn = [block startingColumn] + [block columnSpan];
2219            }
2220        } while (openColumn >= numberOfColumns);
2221        if ((NSUInteger)numberOfColumns > [table numberOfColumns])
2222            [table setNumberOfColumns:numberOfColumns];
2223        [_textTableRows removeLastObject];
2224        [_textTableRows addObject:[NSNumber numberWithInteger:rowNumber]];
2225        [_textTableRowArrays removeLastObject];
2226        [_textTableRowArrays addObject:rowArray];
2227        if ([_textTableRowBackgroundColors count] > 0)
2228            [_textTableRowBackgroundColors removeLastObject];
2229    } else if (displayValue == "table-cell" && [_textBlocks count] > 0) {
2230        while ([_textTables count] > [_textBlocks count]) {
2231            [_textTables removeLastObject];
2232            [_textTableSpacings removeLastObject];
2233            [_textTablePaddings removeLastObject];
2234            [_textTableRows removeLastObject];
2235            [_textTableRowArrays removeLastObject];
2236        }
2237        [_textBlocks removeLastObject];
2238    } else if ((element.hasTagName(ulTag) || element.hasTagName(olTag)) && [_textLists count] > 0) {
2239        NSTextList *list = [_textLists lastObject];
2240        _addMarkersToList(list, range);
2241        [_textLists removeLastObject];
2242    } else if (element.hasTagName(qTag)) {
2243        _addQuoteForElement(element, NO, --_quoteLevel);
2244    } else if (element.hasTagName(spanTag)) {
2245        NSString *className = element.getAttribute(classAttr);
2246        NSMutableString *mutableString;
2247        NSUInteger i, count = 0;
2248        unichar c;
2249        if ([@"Apple-converted-space" isEqualToString:className]) {
2250            mutableString = [_attrStr mutableString];
2251            for (i = range.location; i < NSMaxRange(range); i++) {
2252                c = [mutableString characterAtIndex:i];
2253                if (0xa0 == c)
2254                    [mutableString replaceCharactersInRange:NSMakeRange(i, 1) withString:@" "];
2255            }
2256        } else if ([@"Apple-converted-tab" isEqualToString:className]) {
2257            mutableString = [_attrStr mutableString];
2258            for (i = range.location; i < NSMaxRange(range); i++) {
2259                NSRange rangeToReplace = NSMakeRange(NSNotFound, 0);
2260                c = [mutableString characterAtIndex:i];
2261                if (' ' == c || 0xa0 == c) {
2262                    count++;
2263                    if (count >= 4 || i + 1 >= NSMaxRange(range))
2264                        rangeToReplace = NSMakeRange(i + 1 - count, count);
2265                } else {
2266                    if (count > 0)
2267                        rangeToReplace = NSMakeRange(i - count, count);
2268                }
2269                if (rangeToReplace.length > 0) {
2270                    [mutableString replaceCharactersInRange:rangeToReplace withString:@"\t"];
2271                    range.length -= (rangeToReplace.length - 1);
2272                    i -= (rangeToReplace.length - 1);
2273                    if (NSMaxRange(rangeToReplace) <= _domRangeStartIndex) {
2274                        _domRangeStartIndex -= (rangeToReplace.length - 1);
2275                    } else if (rangeToReplace.location < _domRangeStartIndex) {
2276                        _domRangeStartIndex = rangeToReplace.location;
2277                    }
2278                    count = 0;
2279                }
2280            }
2281        }
2282    }
2283}
2284
2285void HTMLConverter::_processText(CharacterData& characterData)
2286{
2287    NSUInteger textLength = [_attrStr length];
2288    unichar lastChar = (textLength > 0) ? [[_attrStr string] characterAtIndex:textLength - 1] : '\n';
2289    BOOL suppressLeadingSpace = ((_flags.isSoft && lastChar == ' ') || lastChar == '\n' || lastChar == '\r' || lastChar == '\t' || lastChar == NSParagraphSeparatorCharacter || lastChar == NSLineSeparatorCharacter || lastChar == NSFormFeedCharacter || lastChar == WebNextLineCharacter);
2290    NSRange rangeToReplace = NSMakeRange(textLength, 0);
2291    CFMutableStringRef mutstr = NULL;
2292
2293    String originalString = characterData.data();
2294    unsigned startOffset = 0;
2295    unsigned endOffset = originalString.length();
2296    if (&characterData == m_range->startContainer()) {
2297        startOffset = m_range->startOffset();
2298        _domRangeStartIndex = [_attrStr length];
2299        _flags.reachedStart = YES;
2300    }
2301    if (&characterData == m_range->endContainer()) {
2302        endOffset = m_range->endOffset();
2303        _flags.reachedEnd = YES;
2304    }
2305    if ((startOffset > 0 || endOffset < originalString.length()) && endOffset >= startOffset)
2306        originalString = originalString.substring(startOffset, endOffset - startOffset);
2307    String outputString = originalString;
2308
2309    // FIXME: Use RenderText's content instead.
2310    bool wasSpace = false;
2311    if (_caches->propertyValueForNode(characterData, CSSPropertyWhiteSpace).startsWith("pre")) {
2312        if (textLength && originalString.length() && _flags.isSoft) {
2313            unichar c = originalString.at(0);
2314            if (c == '\n' || c == '\r' || c == NSParagraphSeparatorCharacter || c == NSLineSeparatorCharacter || c == NSFormFeedCharacter || c == WebNextLineCharacter)
2315                rangeToReplace = NSMakeRange(textLength - 1, 1);
2316        }
2317    } else {
2318        unsigned count = originalString.length();
2319        bool wasLeading = true;
2320        StringBuilder builder;
2321        for (unsigned i = 0; i < count; i++) {
2322            UChar c = originalString.at(i);
2323            bool isWhitespace = c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == 0xc || c == 0x200b;
2324            if (isWhitespace)
2325                wasSpace = (!wasLeading || !suppressLeadingSpace);
2326            else {
2327                if (wasSpace)
2328                    builder.append(' ');
2329                builder.append(c);
2330                wasSpace = false;
2331                wasLeading = false;
2332            }
2333        }
2334        if (wasSpace)
2335            builder.append(' ');
2336        outputString = builder.toString();
2337    }
2338
2339    if (outputString.length()) {
2340        String textTransform = _caches->propertyValueForNode(characterData, CSSPropertyTextTransform);
2341        if (textTransform.length()) {
2342            if (textTransform == "capitalize") {// FIXME: This is extremely inefficient.
2343                NSString *temporaryString = outputString;
2344                outputString = [temporaryString capitalizedString];
2345            } else if (textTransform == "uppercase")
2346                outputString = outputString.upper();
2347            else if (textTransform == "lowercase")
2348                outputString = outputString.lower();
2349        }
2350
2351        [_attrStr replaceCharactersInRange:rangeToReplace withString:outputString];
2352        rangeToReplace.length = outputString.length();
2353        if (rangeToReplace.length)
2354            [_attrStr setAttributes:aggregatedAttributesForAncestors(characterData) range:rangeToReplace];
2355        _flags.isSoft = wasSpace;
2356    }
2357    if (mutstr)
2358        CFRelease(mutstr);
2359}
2360
2361void HTMLConverter::_traverseNode(Node& node, unsigned depth, bool embedded)
2362{
2363    if (_flags.reachedEnd)
2364        return;
2365    if (!_flags.reachedStart && !_caches->isAncestorsOfStartToBeConverted(node))
2366        return;
2367
2368    unsigned startOffset = 0;
2369    unsigned endOffset = UINT_MAX;
2370    bool isStart = false;
2371    bool isEnd = false;
2372    if (&node == m_range->startContainer()) {
2373        startOffset = m_range->startOffset();
2374        isStart = true;
2375        _flags.reachedStart = YES;
2376    }
2377    if (&node == m_range->endContainer()) {
2378        endOffset = m_range->endOffset();
2379        isEnd = true;
2380    }
2381
2382    if (node.isDocumentNode() || node.isDocumentFragment()) {
2383        Node* child = node.firstChild();
2384        for (NSUInteger i = 0; child; i++) {
2385            if (isStart && i == startOffset)
2386                _domRangeStartIndex = [_attrStr length];
2387            if ((!isStart || startOffset <= i) && (!isEnd || endOffset > i))
2388                _traverseNode(*child, depth + 1, embedded);
2389            if (isEnd && i + 1 >= endOffset)
2390                _flags.reachedEnd = YES;
2391            if (_flags.reachedEnd)
2392                break;
2393            child = child->nextSibling();
2394        }
2395    } else if (node.isElementNode()) {
2396        Element& element = toElement(node);
2397        if (_enterElement(element, embedded)) {
2398            NSUInteger startIndex = [_attrStr length];
2399            if (_processElement(element, depth)) {
2400                Node* child = node.firstChild();
2401                for (NSUInteger i = 0; child; i++) {
2402                    if (isStart && i == startOffset)
2403                        _domRangeStartIndex = [_attrStr length];
2404                    if ((!isStart || startOffset <= i) && (!isEnd || endOffset > i))
2405                        _traverseNode(*child, depth + 1, embedded);
2406                    if (isEnd && i + 1 >= endOffset)
2407                        _flags.reachedEnd = YES;
2408                    if (_flags.reachedEnd)
2409                        break;
2410                    child = child->nextSibling();
2411                }
2412                _exitElement(element, depth, startIndex);
2413            }
2414        }
2415    } else if (node.isCharacterDataNode())
2416        _processText(toCharacterData(node));
2417
2418    if (isEnd)
2419        _flags.reachedEnd = YES;
2420}
2421
2422void HTMLConverter::_traverseFooterNode(Element& element, unsigned depth)
2423{
2424    if (_flags.reachedEnd)
2425        return;
2426    if (!_flags.reachedStart && !_caches->isAncestorsOfStartToBeConverted(element))
2427        return;
2428
2429    unsigned startOffset = 0;
2430    unsigned endOffset = UINT_MAX;
2431    bool isStart = false;
2432    bool isEnd = false;
2433    if (&element == m_range->startContainer()) {
2434        startOffset = m_range->startOffset();
2435        isStart = true;
2436        _flags.reachedStart = YES;
2437    }
2438    if (&element == m_range->endContainer()) {
2439        endOffset = m_range->endOffset();
2440        isEnd = true;
2441    }
2442
2443    if (_enterElement(element, YES)) {
2444        NSUInteger startIndex = [_attrStr length];
2445        if (_processElement(element, depth)) {
2446            Node* child = element.firstChild();
2447            for (NSUInteger i = 0; child; i++) {
2448                if (isStart && i == startOffset)
2449                    _domRangeStartIndex = [_attrStr length];
2450                if ((!isStart || startOffset <= i) && (!isEnd || endOffset > i))
2451                    _traverseNode(*child, depth + 1, true /* embedded */);
2452                if (isEnd && i + 1 >= endOffset)
2453                    _flags.reachedEnd = YES;
2454                if (_flags.reachedEnd)
2455                    break;
2456                child = child->nextSibling();
2457            }
2458            _exitElement(element, depth, startIndex);
2459        }
2460    }
2461    if (isEnd)
2462        _flags.reachedEnd = YES;
2463}
2464
2465void HTMLConverter::_adjustTrailingNewline()
2466{
2467    NSUInteger textLength = [_attrStr length];
2468    unichar lastChar = (textLength > 0) ? [[_attrStr string] characterAtIndex:textLength - 1] : 0;
2469    BOOL alreadyHasTrailingNewline = (lastChar == '\n' || lastChar == '\r' || lastChar == NSParagraphSeparatorCharacter || lastChar == NSLineSeparatorCharacter || lastChar == WebNextLineCharacter);
2470    if (_flags.hasTrailingNewline && !alreadyHasTrailingNewline)
2471        [_attrStr replaceCharactersInRange:NSMakeRange(textLength, 0) withString:@"\n"];
2472}
2473
2474Node* HTMLConverterCaches::cacheAncestorsOfStartToBeConverted(const Range& range)
2475{
2476    Node* commonAncestor = range.commonAncestorContainer(ASSERT_NO_EXCEPTION);
2477    Node* ancestor = range.startContainer();
2478
2479    while (ancestor) {
2480        m_ancestorsUnderCommonAncestor.add(ancestor);
2481        if (ancestor == commonAncestor)
2482            break;
2483        ancestor = ancestor->parentNode();
2484    }
2485
2486    return commonAncestor;
2487}
2488
2489#if !PLATFORM(IOS)
2490
2491static NSFileWrapper *fileWrapperForURL(DocumentLoader *dataSource, NSURL *URL)
2492{
2493    if ([URL isFileURL])
2494        return [[[NSFileWrapper alloc] initWithURL:[URL URLByResolvingSymlinksInPath] options:0 error:nullptr] autorelease];
2495
2496    RefPtr<ArchiveResource> resource = dataSource->subresource(URL);
2497    if (resource) {
2498        NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:resource->data()->createNSData().get()] autorelease];
2499        NSString *filename = resource->response().suggestedFilename();
2500        if (!filename || ![filename length])
2501            filename = suggestedFilenameWithMIMEType(resource->url(), resource->mimeType());
2502        [wrapper setPreferredFilename:filename];
2503        return wrapper;
2504    }
2505
2506    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
2507
2508    NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
2509    [request release];
2510
2511    if (cachedResponse) {
2512        NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:[cachedResponse data]] autorelease];
2513        [wrapper setPreferredFilename:[[cachedResponse response] suggestedFilename]];
2514        return wrapper;
2515    }
2516
2517    return nil;
2518}
2519
2520static NSFileWrapper *fileWrapperForElement(Element* element)
2521{
2522    NSFileWrapper *wrapper = nil;
2523
2524    const AtomicString& attr = element->getAttribute(srcAttr);
2525    if (!attr.isEmpty()) {
2526        NSURL *URL = element->document().completeURL(attr);
2527        if (DocumentLoader* loader = element->document().loader())
2528            wrapper = fileWrapperForURL(loader, URL);
2529    }
2530    if (!wrapper) {
2531        RenderImage* renderer = toRenderImage(element->renderer());
2532        if (renderer->cachedImage() && !renderer->cachedImage()->errorOccurred()) {
2533            wrapper = [[NSFileWrapper alloc] initRegularFileWithContents:(NSData *)(renderer->cachedImage()->imageForRenderer(renderer)->getTIFFRepresentation())];
2534            [wrapper setPreferredFilename:@"image.tiff"];
2535            [wrapper autorelease];
2536        }
2537    }
2538
2539    return wrapper;
2540}
2541
2542#endif
2543
2544namespace WebCore {
2545
2546// This function supports more HTML features than the editing variant below, such as tables.
2547NSAttributedString *attributedStringFromRange(Range& range)
2548{
2549    HTMLConverter converter(range);
2550    return converter.convert();
2551}
2552
2553#if !PLATFORM(IOS)
2554// This function uses TextIterator, which makes offsets in its result compatible with HTML editing.
2555NSAttributedString *editingAttributedStringFromRange(Range& range)
2556{
2557    NSFontManager *fontManager = [NSFontManager sharedFontManager];
2558    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] init];
2559    NSUInteger stringLength = 0;
2560    RetainPtr<NSMutableDictionary> attrs = adoptNS([[NSMutableDictionary alloc] init]);
2561
2562    for (TextIterator it(&range); !it.atEnd(); it.advance()) {
2563        RefPtr<Range> currentTextRange = it.range();
2564        Node* startContainer = currentTextRange->startContainer();
2565        Node* endContainer = currentTextRange->endContainer();
2566        int startOffset = currentTextRange->startOffset();
2567        int endOffset = currentTextRange->endOffset();
2568
2569        if (startContainer == endContainer && (startOffset == endOffset - 1)) {
2570            Node* node = startContainer->childNode(startOffset);
2571            if (node && node->hasTagName(imgTag)) {
2572                NSFileWrapper* fileWrapper = fileWrapperForElement(toElement(node));
2573                NSTextAttachment* attachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper];
2574                [string appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];
2575                [attachment release];
2576            }
2577        }
2578
2579        int currentTextLength = it.text().length();
2580        if (!currentTextLength)
2581            continue;
2582
2583        RenderObject* renderer = startContainer->renderer();
2584        ASSERT(renderer);
2585        if (!renderer)
2586            continue;
2587        const RenderStyle& style = renderer->style();
2588        if (style.textDecorationsInEffect() & TextDecorationUnderline)
2589            [attrs.get() setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName];
2590        if (style.textDecorationsInEffect() & TextDecorationLineThrough)
2591            [attrs.get() setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName];
2592        if (NSFont *font = style.font().primaryFont()->getNSFont())
2593            [attrs.get() setObject:font forKey:NSFontAttributeName];
2594        else
2595            [attrs.get() setObject:[fontManager convertFont:WebDefaultFont() toSize:style.font().primaryFont()->platformData().size()] forKey:NSFontAttributeName];
2596        if (style.visitedDependentColor(CSSPropertyColor).alpha())
2597            [attrs.get() setObject:nsColor(style.visitedDependentColor(CSSPropertyColor)) forKey:NSForegroundColorAttributeName];
2598        else
2599            [attrs.get() removeObjectForKey:NSForegroundColorAttributeName];
2600        if (style.visitedDependentColor(CSSPropertyBackgroundColor).alpha())
2601            [attrs.get() setObject:nsColor(style.visitedDependentColor(CSSPropertyBackgroundColor)) forKey:NSBackgroundColorAttributeName];
2602        else
2603            [attrs.get() removeObjectForKey:NSBackgroundColorAttributeName];
2604
2605        [string replaceCharactersInRange:NSMakeRange(stringLength, 0) withString:it.text().createNSStringWithoutCopying().get()];
2606        [string setAttributes:attrs.get() range:NSMakeRange(stringLength, currentTextLength)];
2607        stringLength += currentTextLength;
2608    }
2609
2610    return [string autorelease];
2611}
2612#endif
2613
2614}
2615