6d4d68ebcd8c033fee50c2ba5285879709bc89a4
[WebKit.git] / Source / WebCore / editing / cocoa / HTMLConverter.mm
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
65 SOFT_LINK_FRAMEWORK(UIKit)
66 SOFT_LINK_CLASS(UIKit, UIColor)
67
68 SOFT_LINK_PRIVATE_FRAMEWORK(UIFoundation)
69 SOFT_LINK_CLASS(UIFoundation, UIFont)
70 SOFT_LINK_CLASS(UIFoundation, NSColor)
71 SOFT_LINK_CLASS(UIFoundation, NSShadow)
72 SOFT_LINK_CLASS(UIFoundation, NSTextAttachment)
73 SOFT_LINK_CLASS(UIFoundation, NSMutableParagraphStyle)
74 SOFT_LINK_CLASS(UIFoundation, NSParagraphStyle)
75 SOFT_LINK_CLASS(UIFoundation, NSTextList)
76 SOFT_LINK_CLASS(UIFoundation, NSTextBlock)
77 SOFT_LINK_CLASS(UIFoundation, NSTextTableBlock)
78 SOFT_LINK_CLASS(UIFoundation, NSTextTable)
79 SOFT_LINK_CLASS(UIFoundation, NSTextTab)
80
81 SOFT_LINK_CONSTANT(UIFoundation, NSFontAttributeName, NSString *)
82 #define NSFontAttributeName getNSFontAttributeName()
83 SOFT_LINK_CONSTANT(UIFoundation, NSForegroundColorAttributeName, NSString *)
84 #define NSForegroundColorAttributeName getNSForegroundColorAttributeName()
85 SOFT_LINK_CONSTANT(UIFoundation, NSBackgroundColorAttributeName, NSString *)
86 #define NSBackgroundColorAttributeName getNSBackgroundColorAttributeName()
87 SOFT_LINK_CONSTANT(UIFoundation, NSStrokeColorAttributeName, NSString *)
88 #define NSStrokeColorAttributeName getNSStrokeColorAttributeName()
89 SOFT_LINK_CONSTANT(UIFoundation, NSStrokeWidthAttributeName, NSString *)
90 #define NSStrokeWidthAttributeName getNSStrokeWidthAttributeName()
91 SOFT_LINK_CONSTANT(UIFoundation, NSShadowAttributeName, NSString *)
92 #define NSShadowAttributeName getNSShadowAttributeName()
93 SOFT_LINK_CONSTANT(UIFoundation, NSKernAttributeName, NSString *)
94 #define NSKernAttributeName getNSKernAttributeName()
95 SOFT_LINK_CONSTANT(UIFoundation, NSLigatureAttributeName, NSString *)
96 #define NSLigatureAttributeName getNSLigatureAttributeName()
97 SOFT_LINK_CONSTANT(UIFoundation, NSUnderlineStyleAttributeName, NSString *)
98 #define NSUnderlineStyleAttributeName getNSUnderlineStyleAttributeName()
99 SOFT_LINK_CONSTANT(UIFoundation, NSStrikethroughStyleAttributeName, NSString *)
100 #define NSStrikethroughStyleAttributeName getNSStrikethroughStyleAttributeName()
101 SOFT_LINK_CONSTANT(UIFoundation, NSBaselineOffsetAttributeName, NSString *)
102 #define NSBaselineOffsetAttributeName getNSBaselineOffsetAttributeName()
103 SOFT_LINK_CONSTANT(UIFoundation, NSWritingDirectionAttributeName, NSString *)
104 #define NSWritingDirectionAttributeName getNSWritingDirectionAttributeName()
105 SOFT_LINK_CONSTANT(UIFoundation, NSParagraphStyleAttributeName, NSString *)
106 #define NSParagraphStyleAttributeName getNSParagraphStyleAttributeName()
107 SOFT_LINK_CONSTANT(UIFoundation, NSAttachmentAttributeName, NSString *)
108 #define NSAttachmentAttributeName getNSAttachmentAttributeName()
109 SOFT_LINK_CONSTANT(UIFoundation, NSLinkAttributeName, NSString *)
110 #define NSLinkAttributeName getNSLinkAttributeName()
111 SOFT_LINK_CONSTANT(UIFoundation, NSAuthorDocumentAttribute, NSString *)
112 #define NSAuthorDocumentAttribute getNSAuthorDocumentAttribute()
113 SOFT_LINK_CONSTANT(UIFoundation, NSEditorDocumentAttribute, NSString *)
114 #define NSEditorDocumentAttribute getNSEditorDocumentAttribute()
115 SOFT_LINK_CONSTANT(UIFoundation, NSGeneratorDocumentAttribute, NSString *)
116 #define NSGeneratorDocumentAttribute getNSGeneratorDocumentAttribute()
117 SOFT_LINK_CONSTANT(UIFoundation, NSCompanyDocumentAttribute, NSString *)
118 #define NSCompanyDocumentAttribute getNSCompanyDocumentAttribute()
119 SOFT_LINK_CONSTANT(UIFoundation, NSDisplayNameDocumentAttribute, NSString *)
120 #define NSDisplayNameDocumentAttribute getNSDisplayNameDocumentAttribute()
121 SOFT_LINK_CONSTANT(UIFoundation, NSCopyrightDocumentAttribute, NSString *)
122 #define NSCopyrightDocumentAttribute getNSCopyrightDocumentAttribute()
123 SOFT_LINK_CONSTANT(UIFoundation, NSSubjectDocumentAttribute, NSString *)
124 #define NSSubjectDocumentAttribute getNSSubjectDocumentAttribute()
125 SOFT_LINK_CONSTANT(UIFoundation, NSCommentDocumentAttribute, NSString *)
126 #define NSCommentDocumentAttribute getNSCommentDocumentAttribute()
127 SOFT_LINK_CONSTANT(UIFoundation, NSNoIndexDocumentAttribute, NSString *)
128 #define NSNoIndexDocumentAttribute getNSNoIndexDocumentAttribute()
129 SOFT_LINK_CONSTANT(UIFoundation, NSKeywordsDocumentAttribute, NSString *)
130 #define NSKeywordsDocumentAttribute getNSKeywordsDocumentAttribute()
131 SOFT_LINK_CONSTANT(UIFoundation, NSCreationTimeDocumentAttribute, NSString *)
132 #define NSCreationTimeDocumentAttribute getNSCreationTimeDocumentAttribute()
133 SOFT_LINK_CONSTANT(UIFoundation, NSModificationTimeDocumentAttribute, NSString *)
134 #define NSModificationTimeDocumentAttribute getNSModificationTimeDocumentAttribute()
135 SOFT_LINK_CONSTANT(UIFoundation, NSConvertedDocumentAttribute, NSString *)
136 #define NSConvertedDocumentAttribute getNSConvertedDocumentAttribute()
137 SOFT_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.
156 static 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
178 using namespace WebCore;
179 using namespace HTMLNames;
180
181 #if PLATFORM(IOS)
182
183 typedef 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
192 typedef 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
207 enum {
208     NSTextBlockAbsoluteValueType    = 0,    // Absolute value in points
209     NSTextBlockPercentageValueType  = 1     // Percentage value (out of 100)
210 };
211 typedef NSUInteger NSTextBlockValueType;
212
213 enum {
214     NSTextBlockWidth            = 0,
215     NSTextBlockMinimumWidth     = 1,
216     NSTextBlockMaximumWidth     = 2,
217     NSTextBlockHeight           = 4,
218     NSTextBlockMinimumHeight    = 5,
219     NSTextBlockMaximumHeight    = 6
220 };
221 typedef NSUInteger NSTextBlockDimension;
222
223 enum {
224     NSTextBlockPadding  = -1,
225     NSTextBlockBorder   =  0,
226     NSTextBlockMargin   =  1
227 };
228 typedef NSInteger NSTextBlockLayer;
229
230 enum {
231     NSTextTableAutomaticLayoutAlgorithm = 0,
232     NSTextTableFixedLayoutAlgorithm     = 1
233 };
234 typedef NSUInteger NSTextTableLayoutAlgorithm;
235
236 enum {
237     NSTextBlockTopAlignment         = 0,
238     NSTextBlockMiddleAlignment      = 1,
239     NSTextBlockBottomAlignment      = 2,
240     NSTextBlockBaselineAlignment    = 3
241 };
242 typedef NSUInteger NSTextBlockVerticalAlignment;
243
244 typedef 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
252 typedef 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
258 typedef NS_ENUM(NSInteger, NSTextWritingDirection) {
259     NSTextWritingDirectionEmbedding     = (0 << 1),
260     NSTextWritingDirectionOverride      = (1 << 1)
261 } NS_ENUM_AVAILABLE_IOS(7_0);
262
263 enum {
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
277 enum {
278     NSLeftTabStopType = 0,
279     NSRightTabStopType,
280     NSCenterTabStopType,
281     NSDecimalTabStopType
282 };
283 typedef 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
389 static NSFileWrapper *fileWrapperForURL(DocumentLoader *, NSURL *);
390 static 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
401 const unichar WebNextLineCharacter = 0x0085;
402
403 static const CGFloat defaultFontSize = 12;
404 static const CGFloat minimumFontSize = 1;
405
406 class HTMLConverterCaches {
407 public:
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
421 private:
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
439 class HTMLConverter {
440 public:
441     HTMLConverter(Range&);
442     ~HTMLConverter();
443     
444     NSAttributedString* convert();
445     
446 private:
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
516 HTMLConverter::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
545 HTMLConverter::~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
561 NSAttributedString *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
580 static 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
595 static 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
685 static 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
696 PassRefPtr<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
708 PassRefPtr<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
718 static 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
737 String 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
855 static 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
882 bool 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)
926 static 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
932 static NSString *_NSSystemLibraryPath(void)
933 {
934     return _NSFirstPathForDirectoriesInDomains(NSLibraryDirectory, NSSystemDomainMask, YES);
935 }
936
937 static 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
946 static inline UIColor *_platformColor(Color color)
947 {
948     return [getUIColorClass() _disambiguated_due_to_CIImage_colorWithCGColor:cachedCGColor(color, WebCore::ColorSpaceDeviceRGB)];
949 }
950 #else
951 static inline NSColor *_platformColor(Color color)
952 {
953     return nsColor(color);
954 }
955 #endif
956
957 static 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
1011 bool 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
1022 bool 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
1031 Element* 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
1039 static 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
1051 Color 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
1097 PlatformColor *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)
1109 static 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
1117 static 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
1128 NSDictionary *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
1346 NSDictionary* 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
1354 NSDictionary* 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
1364 NSDictionary* 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
1389 void 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
1409 void 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
1425 void 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
1441 static 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
1455 BOOL 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
1557 void 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
1573 void 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
1590 void 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
1672 static 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
1686 static 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
1731 static 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
1738 void 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
1801 void 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
1813 BOOL 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
1828 void 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
1868 void 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
1923 BOOL 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
2063 void 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
2151 void 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
2285 void 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
2361 void 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
2422 void 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
2465 void 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
2474 Node* 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
2491 static 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
2520 static 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
2544 namespace WebCore {
2545     
2546 // This function supports more HTML features than the editing variant below, such as tables.
2547 NSAttributedString *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.
2555 NSAttributedString *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 }