webengine/osswebengine/WebKit/Misc/WebNSAttributedStringExtras.mm
changeset 0 dd21522fd290
equal deleted inserted replaced
-1:000000000000 0:dd21522fd290
       
     1 /*
       
     2  * Copyright (C) 2005 Apple Computer, 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  *
       
     8  * 1.  Redistributions of source code must retain the above copyright
       
     9  *     notice, this list of conditions and the following disclaimer. 
       
    10  * 2.  Redistributions in binary form must reproduce the above copyright
       
    11  *     notice, this list of conditions and the following disclaimer in the
       
    12  *     documentation and/or other materials provided with the distribution. 
       
    13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
       
    14  *     its contributors may be used to endorse or promote products derived
       
    15  *     from this software without specific prior written permission. 
       
    16  *
       
    17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
       
    18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
    19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
       
    20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
       
    21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
       
    22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
       
    23     * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
       
    24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       
    25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
       
    26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    27  */
       
    28 
       
    29 #import "WebNSAttributedStringExtras.h"
       
    30 
       
    31 #import "DOMRangeInternal.h"
       
    32 #import "WebDataSourcePrivate.h"
       
    33 #import "WebFrame.h"
       
    34 #import "WebFrameBridge.h"
       
    35 #import "WebFrameInternal.h"
       
    36 #import <WebCore/BlockExceptions.h>
       
    37 #import <WebCore/ColorMac.h>
       
    38 #import <WebCore/CSSHelper.h>
       
    39 #import <WebCore/Document.h>
       
    40 #import <WebCore/Element.h>
       
    41 #import <WebCore/FontData.h>
       
    42 #import <WebCore/Frame.h>
       
    43 #import <WebCore/FrameLoader.h>
       
    44 #import <WebCore/HTMLNames.h>
       
    45 #import <WebCore/Image.h>
       
    46 #import <WebCore/InlineTextBox.h>
       
    47 #import <WebCore/KURL.h>
       
    48 #import <WebCore/Range.h>
       
    49 #import <WebCore/RenderImage.h>
       
    50 #import <WebCore/RenderListItem.h>
       
    51 #import <WebCore/RenderObject.h>
       
    52 #import <WebCore/RenderStyle.h>
       
    53 #import <WebCore/RenderText.h>
       
    54 #import <WebCore/Text.h>
       
    55 
       
    56 using namespace WebCore;
       
    57 using namespace HTMLNames;
       
    58 
       
    59 struct ListItemInfo {
       
    60     unsigned start;
       
    61     unsigned end;
       
    62 };
       
    63 
       
    64 static Element* listParent(Element* item)
       
    65 {
       
    66     while (!item->hasTagName(ulTag) && !item->hasTagName(olTag)) {
       
    67         item = static_cast<Element*>(item->parentNode());
       
    68         if (!item)
       
    69             break;
       
    70     }
       
    71     return item;
       
    72 }
       
    73 
       
    74 static Node* isTextFirstInListItem(Node* e)
       
    75 {
       
    76     if (!e->isTextNode())
       
    77         return 0;
       
    78     Node* par = e->parentNode();
       
    79     while (par) {
       
    80         if (par->firstChild() != e)
       
    81             return 0;
       
    82         if (par->hasTagName(liTag))
       
    83             return par;
       
    84         e = par;
       
    85         par = par->parentNode();
       
    86     }
       
    87     return 0;
       
    88 }
       
    89 
       
    90 static NSFileWrapper *fileWrapperForElement(Element* e)
       
    91 {
       
    92     NSFileWrapper *wrapper = nil;
       
    93     BEGIN_BLOCK_OBJC_EXCEPTIONS;
       
    94     
       
    95     const AtomicString& attr = e->getAttribute(srcAttr);
       
    96     if (!attr.isEmpty()) {
       
    97         NSURL *URL = KURL(e->document()->completeURL(attr.deprecatedString())).getNSURL();
       
    98         wrapper = [[kit(e->document()->frame()) _dataSource] _fileWrapperForURL:URL];
       
    99     }
       
   100     if (!wrapper) {
       
   101         RenderImage* renderer = static_cast<RenderImage*>(e->renderer());
       
   102         if (renderer->cachedImage() && !renderer->cachedImage()->errorOccurred()) {
       
   103             wrapper = [[NSFileWrapper alloc] initRegularFileWithContents:(NSData *)(renderer->cachedImage()->image()->getTIFFRepresentation())];
       
   104             [wrapper setPreferredFilename:@"image.tiff"];
       
   105             [wrapper autorelease];
       
   106         }
       
   107     }
       
   108 
       
   109     return wrapper;
       
   110 
       
   111     END_BLOCK_OBJC_EXCEPTIONS;
       
   112 
       
   113     return nil;
       
   114 }
       
   115 
       
   116 @implementation NSAttributedString (WebKitExtras)
       
   117 
       
   118 - (NSAttributedString *)_web_attributedStringByStrippingAttachmentCharacters
       
   119 {
       
   120     // This code was originally copied from NSTextView
       
   121     NSRange attachmentRange;
       
   122     NSString *originalString = [self string];
       
   123     static NSString *attachmentCharString = nil;
       
   124     
       
   125     if (!attachmentCharString) {
       
   126         unichar chars[2];
       
   127         if (!attachmentCharString) {
       
   128             chars[0] = NSAttachmentCharacter;
       
   129             chars[1] = 0;
       
   130             attachmentCharString = [[NSString alloc] initWithCharacters:chars length:1];
       
   131         }
       
   132     }
       
   133     
       
   134     attachmentRange = [originalString rangeOfString:attachmentCharString];
       
   135     if (attachmentRange.location != NSNotFound && attachmentRange.length > 0) {
       
   136         NSMutableAttributedString *newAttributedString = [[self mutableCopyWithZone:NULL] autorelease];
       
   137         
       
   138         while (attachmentRange.location != NSNotFound && attachmentRange.length > 0) {
       
   139             [newAttributedString replaceCharactersInRange:attachmentRange withString:@""];
       
   140             attachmentRange = [[newAttributedString string] rangeOfString:attachmentCharString];
       
   141         }
       
   142         return newAttributedString;
       
   143     }
       
   144     
       
   145     return self;
       
   146 }
       
   147 
       
   148 // FIXME: Use WebCore::TextIterator to iterate text runs.
       
   149 
       
   150 + (NSAttributedString *)_web_attributedStringFromRange:(Range*)range
       
   151 {
       
   152     ListItemInfo info;
       
   153     ExceptionCode ec = 0; // dummy variable -- we ignore DOM exceptions
       
   154     NSMutableAttributedString *result;
       
   155     BEGIN_BLOCK_OBJC_EXCEPTIONS;
       
   156 
       
   157     if (!range || !range->boundaryPointsValid())
       
   158         return nil;
       
   159     
       
   160     Node* firstNode = range->startNode();
       
   161     if (!firstNode)
       
   162         return nil;
       
   163     Node* pastEndNode = range->pastEndNode();
       
   164     
       
   165     int startOffset = range->startOffset(ec);
       
   166     int endOffset = range->endOffset(ec);
       
   167     Node* endNode = range->endContainer(ec);
       
   168 
       
   169     result = [[[NSMutableAttributedString alloc] init] autorelease];
       
   170     
       
   171     bool hasNewLine = true;
       
   172     bool addedSpace = true;
       
   173     NSAttributedString *pendingStyledSpace = nil;
       
   174     bool hasParagraphBreak = true;
       
   175     const Element *linkStartNode = 0;
       
   176     unsigned linkStartLocation = 0;
       
   177     Vector<Element*> listItems;
       
   178     Vector<ListItemInfo> listItemLocations;
       
   179     float maxMarkerWidth = 0;
       
   180     
       
   181     Node *currentNode = firstNode;
       
   182     
       
   183     // If the first item is the entire text of a list item, use the list item node as the start of the 
       
   184     // selection, not the text node.  The user's intent was probably to select the list.
       
   185     if (currentNode->isTextNode() && startOffset == 0) {
       
   186         Node *startListNode = isTextFirstInListItem(firstNode);
       
   187         if (startListNode){
       
   188             firstNode = startListNode;
       
   189             currentNode = firstNode;
       
   190         }
       
   191     }
       
   192     
       
   193     while (currentNode && currentNode != pastEndNode) {
       
   194         RenderObject *renderer = currentNode->renderer();
       
   195         if (renderer) {
       
   196             RenderStyle *style = renderer->style();
       
   197             NSFont *font = style->font().primaryFont()->getNSFont();
       
   198             bool needSpace = pendingStyledSpace != nil;
       
   199             if (currentNode->isTextNode()) {
       
   200                 if (hasNewLine) {
       
   201                     addedSpace = true;
       
   202                     needSpace = false;
       
   203                     [pendingStyledSpace release];
       
   204                     pendingStyledSpace = nil;
       
   205                     hasNewLine = false;
       
   206                 }
       
   207                 DeprecatedString text;
       
   208                 DeprecatedString str = currentNode->nodeValue().deprecatedString();
       
   209                 int start = (currentNode == firstNode) ? startOffset : -1;
       
   210                 int end = (currentNode == endNode) ? endOffset : -1;
       
   211                 if (renderer->isText()) {
       
   212                     if (!style->collapseWhiteSpace()) {
       
   213                         if (needSpace && !addedSpace) {
       
   214                             if (text.isEmpty() && linkStartLocation == [result length])
       
   215                                 ++linkStartLocation;
       
   216                             [result appendAttributedString:pendingStyledSpace];
       
   217                         }
       
   218                         int runStart = (start == -1) ? 0 : start;
       
   219                         int runEnd = (end == -1) ? str.length() : end;
       
   220                         text += str.mid(runStart, runEnd-runStart);
       
   221                         [pendingStyledSpace release];
       
   222                         pendingStyledSpace = nil;
       
   223                         addedSpace = u_charDirection(str[runEnd - 1].unicode()) == U_WHITE_SPACE_NEUTRAL;
       
   224                     }
       
   225                     else {
       
   226                         RenderText* textObj = static_cast<RenderText*>(renderer);
       
   227                         if (!textObj->firstTextBox() && str.length() > 0 && !addedSpace) {
       
   228                             // We have no runs, but we do have a length.  This means we must be
       
   229                             // whitespace that collapsed away at the end of a line.
       
   230                             text += ' ';
       
   231                             addedSpace = true;
       
   232                         }
       
   233                         else {
       
   234                             addedSpace = false;
       
   235                             for (InlineTextBox* box = textObj->firstTextBox(); box; box = box->nextTextBox()) {
       
   236                                 int runStart = (start == -1) ? box->m_start : start;
       
   237                                 int runEnd = (end == -1) ? box->m_start + box->m_len : end;
       
   238                                 if (runEnd > box->m_start + box->m_len)
       
   239                                     runEnd = box->m_start + box->m_len;
       
   240                                 if (runStart >= box->m_start &&
       
   241                                     runStart < box->m_start + box->m_len) {
       
   242                                     if (box == textObj->firstTextBox() && box->m_start == runStart && runStart > 0)
       
   243                                         needSpace = true; // collapsed space at the start
       
   244                                     if (needSpace && !addedSpace) {
       
   245                                         if (pendingStyledSpace != nil) {
       
   246                                             if (text.isEmpty() && linkStartLocation == [result length])
       
   247                                                 ++linkStartLocation;
       
   248                                             [result appendAttributedString:pendingStyledSpace];
       
   249                                         } else
       
   250                                             text += ' ';
       
   251                                     }
       
   252                                     DeprecatedString runText = str.mid(runStart, runEnd - runStart);
       
   253                                     runText.replace('\n', ' ');
       
   254                                     text += runText;
       
   255                                     int nextRunStart = box->nextTextBox() ? box->nextTextBox()->m_start : str.length(); // collapsed space between runs or at the end
       
   256                                     needSpace = nextRunStart > runEnd;
       
   257                                     [pendingStyledSpace release];
       
   258                                     pendingStyledSpace = nil;
       
   259                                     addedSpace = u_charDirection(str[runEnd - 1].unicode()) == U_WHITE_SPACE_NEUTRAL;
       
   260                                     start = -1;
       
   261                                 }
       
   262                                 if (end != -1 && runEnd >= end)
       
   263                                     break;
       
   264                             }
       
   265                         }
       
   266                     }
       
   267                 }
       
   268                 
       
   269                 text.replace('\\', renderer->backslashAsCurrencySymbol());
       
   270     
       
   271                 if (text.length() > 0 || needSpace) {
       
   272                     NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
       
   273                     [attrs setObject:font forKey:NSFontAttributeName];
       
   274                     if (style && style->color().isValid() && style->color().alpha() != 0)
       
   275                         [attrs setObject:nsColor(style->color()) forKey:NSForegroundColorAttributeName];
       
   276                     if (style && style->backgroundColor().isValid() && style->backgroundColor().alpha() != 0)
       
   277                         [attrs setObject:nsColor(style->backgroundColor()) forKey:NSBackgroundColorAttributeName];
       
   278 
       
   279                     if (text.length() > 0) {
       
   280                         hasParagraphBreak = false;
       
   281                         NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString() attributes:attrs];
       
   282                         [result appendAttributedString: partialString];                
       
   283                         [partialString release];
       
   284                     }
       
   285 
       
   286                     if (needSpace) {
       
   287                         [pendingStyledSpace release];
       
   288                         pendingStyledSpace = [[NSAttributedString alloc] initWithString:@" " attributes:attrs];
       
   289                     }
       
   290 
       
   291                     [attrs release];
       
   292                 }
       
   293             } else {
       
   294                 // This is our simple HTML -> ASCII transformation:
       
   295                 DeprecatedString text;
       
   296                 if (currentNode->hasTagName(aTag)) {
       
   297                     // Note the start of the <a> element.  We will add the NSLinkAttributeName
       
   298                     // attribute to the attributed string when navigating to the next sibling 
       
   299                     // of this node.
       
   300                     linkStartLocation = [result length];
       
   301                     linkStartNode = static_cast<Element*>(currentNode);
       
   302                 } else if (currentNode->hasTagName(brTag)) {
       
   303                     text += "\n";
       
   304                     hasNewLine = true;
       
   305                 } else if (currentNode->hasTagName(liTag)) {
       
   306                     DeprecatedString listText;
       
   307                     Element *itemParent = listParent(static_cast<Element*>(currentNode));
       
   308                     
       
   309                     if (!hasNewLine)
       
   310                         listText += '\n';
       
   311                     hasNewLine = true;
       
   312 
       
   313                     listItems.append(static_cast<Element*>(currentNode));
       
   314                     info.start = [result length];
       
   315                     info.end = 0;
       
   316                     listItemLocations.append (info);
       
   317                     
       
   318                     listText += '\t';
       
   319                     if (itemParent && renderer->isListItem()) {
       
   320                         RenderListItem* listRenderer = static_cast<RenderListItem*>(renderer);
       
   321 
       
   322                         maxMarkerWidth = MAX([font pointSize], maxMarkerWidth);
       
   323 
       
   324                         String marker = listRenderer->markerText();
       
   325                         if (!marker.isEmpty()) {
       
   326                             listText += marker.deprecatedString();
       
   327                             // Use AppKit metrics, since this will be rendered by AppKit.
       
   328                             NSString *markerNSString = marker;
       
   329                             float markerWidth = [markerNSString sizeWithAttributes:[NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]].width;
       
   330                             maxMarkerWidth = MAX(markerWidth, maxMarkerWidth);
       
   331                         }
       
   332 
       
   333                         listText += ' ';
       
   334                         listText += '\t';
       
   335 
       
   336                         NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
       
   337                         [attrs setObject:font forKey:NSFontAttributeName];
       
   338                         if (style && style->color().isValid())
       
   339                             [attrs setObject:nsColor(style->color()) forKey:NSForegroundColorAttributeName];
       
   340                         if (style && style->backgroundColor().isValid())
       
   341                             [attrs setObject:nsColor(style->backgroundColor()) forKey:NSBackgroundColorAttributeName];
       
   342 
       
   343                         NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:listText.getNSString() attributes:attrs];
       
   344                         [attrs release];
       
   345                         [result appendAttributedString: partialString];                
       
   346                         [partialString release];
       
   347                     }
       
   348                 } else if (currentNode->hasTagName(olTag) || currentNode->hasTagName(ulTag)) {
       
   349                     if (!hasNewLine)
       
   350                         text += "\n";
       
   351                     hasNewLine = true;
       
   352                 } else if (currentNode->hasTagName(blockquoteTag)
       
   353                         || currentNode->hasTagName(ddTag)
       
   354                         || currentNode->hasTagName(divTag)
       
   355                         || currentNode->hasTagName(dlTag)
       
   356                         || currentNode->hasTagName(dtTag)
       
   357                         || currentNode->hasTagName(hrTag)
       
   358                         || currentNode->hasTagName(listingTag)
       
   359                         || currentNode->hasTagName(preTag)
       
   360                         || currentNode->hasTagName(tdTag)
       
   361                         || currentNode->hasTagName(thTag)) {
       
   362                     if (!hasNewLine)
       
   363                         text += '\n';
       
   364                     hasNewLine = true;
       
   365                 } else if (currentNode->hasTagName(h1Tag)
       
   366                         || currentNode->hasTagName(h2Tag)
       
   367                         || currentNode->hasTagName(h3Tag)
       
   368                         || currentNode->hasTagName(h4Tag)
       
   369                         || currentNode->hasTagName(h5Tag)
       
   370                         || currentNode->hasTagName(h6Tag)
       
   371                         || currentNode->hasTagName(pTag)
       
   372                         || currentNode->hasTagName(trTag)) {
       
   373                     if (!hasNewLine)
       
   374                         text += '\n';
       
   375                     
       
   376                     // In certain cases, emit a paragraph break.
       
   377                     int bottomMargin = renderer->collapsedMarginBottom();
       
   378                     int fontSize = style->fontDescription().computedPixelSize();
       
   379                     if (bottomMargin * 2 >= fontSize) {
       
   380                         if (!hasParagraphBreak) {
       
   381                             text += '\n';
       
   382                             hasParagraphBreak = true;
       
   383                         }
       
   384                     }
       
   385                     
       
   386                     hasNewLine = true;
       
   387                 }
       
   388                 else if (currentNode->hasTagName(imgTag)) {
       
   389                     if (pendingStyledSpace != nil) {
       
   390                         if (linkStartLocation == [result length])
       
   391                             ++linkStartLocation;
       
   392                         [result appendAttributedString:pendingStyledSpace];
       
   393                         [pendingStyledSpace release];
       
   394                         pendingStyledSpace = nil;
       
   395                     }
       
   396                     NSFileWrapper *fileWrapper = fileWrapperForElement(static_cast<Element*>(currentNode));
       
   397                     NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper];
       
   398                     NSAttributedString *iString = [NSAttributedString attributedStringWithAttachment:attachment];
       
   399                     [result appendAttributedString: iString];
       
   400                     [attachment release];
       
   401                 }
       
   402 
       
   403                 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString()];
       
   404                 [result appendAttributedString: partialString];
       
   405                 [partialString release];
       
   406             }
       
   407         }
       
   408 
       
   409         Node *nextNode = currentNode->firstChild();
       
   410         if (!nextNode)
       
   411             nextNode = currentNode->nextSibling();
       
   412 
       
   413         while (!nextNode && currentNode->parentNode()) {
       
   414             DeprecatedString text;
       
   415             currentNode = currentNode->parentNode();
       
   416             if (currentNode == pastEndNode)
       
   417                 break;
       
   418             nextNode = currentNode->nextSibling();
       
   419 
       
   420             if (currentNode->hasTagName(aTag)) {
       
   421                 // End of a <a> element.  Create an attributed string NSLinkAttributeName attribute
       
   422                 // for the range of the link.  Note that we create the attributed string from the DOM, which
       
   423                 // will have corrected any illegally nested <a> elements.
       
   424                 if (linkStartNode && currentNode == linkStartNode) {
       
   425                     String href = parseURL(linkStartNode->getAttribute(hrefAttr));
       
   426                     KURL kURL = linkStartNode->document()->frame()->loader()->completeURL(href.deprecatedString());
       
   427                     
       
   428                     NSURL *URL = kURL.getNSURL();
       
   429                     NSRange tempRange = { linkStartLocation, [result length]-linkStartLocation }; // workaround for 4213314
       
   430                     [result addAttribute:NSLinkAttributeName value:URL range:tempRange];
       
   431                     linkStartNode = 0;
       
   432                 }
       
   433             }
       
   434             else if (currentNode->hasTagName(olTag) || currentNode->hasTagName(ulTag)) {
       
   435                 if (!hasNewLine)
       
   436                     text += '\n';
       
   437                 hasNewLine = true;
       
   438             } else if (currentNode->hasTagName(liTag)) {
       
   439                 
       
   440                 int i, count = listItems.size();
       
   441                 for (i = 0; i < count; i++){
       
   442                     if (listItems[i] == currentNode){
       
   443                         listItemLocations[i].end = [result length];
       
   444                         break;
       
   445                     }
       
   446                 }
       
   447                 if (!hasNewLine)
       
   448                     text += '\n';
       
   449                 hasNewLine = true;
       
   450             } else if (currentNode->hasTagName(blockquoteTag) ||
       
   451                        currentNode->hasTagName(ddTag) ||
       
   452                        currentNode->hasTagName(divTag) ||
       
   453                        currentNode->hasTagName(dlTag) ||
       
   454                        currentNode->hasTagName(dtTag) ||
       
   455                        currentNode->hasTagName(hrTag) ||
       
   456                        currentNode->hasTagName(listingTag) ||
       
   457                        currentNode->hasTagName(preTag) ||
       
   458                        currentNode->hasTagName(tdTag) ||
       
   459                        currentNode->hasTagName(thTag)) {
       
   460                 if (!hasNewLine)
       
   461                     text += '\n';
       
   462                 hasNewLine = true;
       
   463             } else if (currentNode->hasTagName(pTag) ||
       
   464                        currentNode->hasTagName(trTag) ||
       
   465                        currentNode->hasTagName(h1Tag) ||
       
   466                        currentNode->hasTagName(h2Tag) ||
       
   467                        currentNode->hasTagName(h3Tag) ||
       
   468                        currentNode->hasTagName(h4Tag) ||
       
   469                        currentNode->hasTagName(h5Tag) ||
       
   470                        currentNode->hasTagName(h6Tag)) {
       
   471                 if (!hasNewLine)
       
   472                     text += '\n';
       
   473                 // An extra newline is needed at the start, not the end, of these types of tags,
       
   474                 // so don't add another here.
       
   475                 hasNewLine = true;
       
   476             }
       
   477             
       
   478             NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString()];
       
   479             [result appendAttributedString:partialString];
       
   480             [partialString release];
       
   481         }
       
   482 
       
   483         currentNode = nextNode;
       
   484     }
       
   485     
       
   486     [pendingStyledSpace release];
       
   487     
       
   488     // Apply paragraph styles from outside in.  This ensures that nested lists correctly
       
   489     // override their parent's paragraph style.
       
   490     {
       
   491         unsigned i, count = listItems.size();
       
   492         Element *e;
       
   493 
       
   494 #ifdef POSITION_LIST
       
   495         Node *containingBlock;
       
   496         int containingBlockX, containingBlockY;
       
   497         
       
   498         // Determine the position of the outermost containing block.  All paragraph
       
   499         // styles and tabs should be relative to this position.  So, the horizontal position of 
       
   500         // each item in the list (in the resulting attributed string) will be relative to position 
       
   501         // of the outermost containing block.
       
   502         if (count > 0){
       
   503             containingBlock = firstNode;
       
   504             while (containingBlock->renderer()->isInline()){
       
   505                 containingBlock = containingBlock->parentNode();
       
   506             }
       
   507             containingBlock->renderer()->absolutePosition(containingBlockX, containingBlockY);
       
   508         }
       
   509 #endif
       
   510         
       
   511         for (i = 0; i < count; i++){
       
   512             e = listItems[i];
       
   513             info = listItemLocations[i];
       
   514             
       
   515             if (info.end < info.start)
       
   516                 info.end = [result length];
       
   517                 
       
   518             RenderObject *r = e->renderer();
       
   519             RenderStyle *style = r->style();
       
   520 
       
   521             int rx;
       
   522             NSFont *font = style->font().primaryFont()->getNSFont();
       
   523             float pointSize = [font pointSize];
       
   524 
       
   525 #ifdef POSITION_LIST
       
   526             int ry;
       
   527             r->absolutePosition(rx, ry);
       
   528             rx -= containingBlockX;
       
   529             
       
   530             // Ensure that the text is indented at least enough to allow for the markers.
       
   531             rx = MAX(rx, (int)maxMarkerWidth);
       
   532 #else
       
   533             rx = (int)MAX(maxMarkerWidth, pointSize);
       
   534 #endif
       
   535 
       
   536             // The bullet text will be right aligned at the first tab marker, followed
       
   537             // by a space, followed by the list item text.  The space is arbitrarily
       
   538             // picked as pointSize*2/3.  The space on the first line of the text item
       
   539             // is established by a left aligned tab, on subsequent lines it's established
       
   540             // by the head indent.
       
   541             NSMutableParagraphStyle *mps = [[NSMutableParagraphStyle alloc] init];
       
   542             [mps setFirstLineHeadIndent: 0];
       
   543             [mps setHeadIndent: rx];
       
   544             [mps setTabStops:[NSArray arrayWithObjects:
       
   545                         [[[NSTextTab alloc] initWithType:NSRightTabStopType location:rx-(pointSize*2/3)] autorelease],
       
   546                         [[[NSTextTab alloc] initWithType:NSLeftTabStopType location:rx] autorelease],
       
   547                         nil]];
       
   548             NSRange tempRange = { info.start, info.end-info.start }; // workaround for 4213314
       
   549             [result addAttribute:NSParagraphStyleAttributeName value:mps range:tempRange];
       
   550             [mps release];
       
   551         }
       
   552     }
       
   553 
       
   554     return result;
       
   555 
       
   556     END_BLOCK_OBJC_EXCEPTIONS;
       
   557 
       
   558     return nil;
       
   559 }
       
   560 
       
   561 @end