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