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