diff options
author | Upstream <upstream-import@none> | 1970-01-12 13:46:40 +0000 |
---|---|---|
committer | Upstream <upstream-import@none> | 1970-01-12 13:46:40 +0000 |
commit | d8543bb6618c17b12da906afa77d216f58cf4058 (patch) | |
tree | c58dc05ed86825bd0ef8d305d58c8205106b540f /WebKit/mac/Misc/WebNSAttributedStringExtras.mm | |
download | external_webkit-d8543bb6618c17b12da906afa77d216f58cf4058.zip external_webkit-d8543bb6618c17b12da906afa77d216f58cf4058.tar.gz external_webkit-d8543bb6618c17b12da906afa77d216f58cf4058.tar.bz2 |
external/webkit r30707
Diffstat (limited to 'WebKit/mac/Misc/WebNSAttributedStringExtras.mm')
-rw-r--r-- | WebKit/mac/Misc/WebNSAttributedStringExtras.mm | 556 |
1 files changed, 556 insertions, 0 deletions
diff --git a/WebKit/mac/Misc/WebNSAttributedStringExtras.mm b/WebKit/mac/Misc/WebNSAttributedStringExtras.mm new file mode 100644 index 0000000..5e50109 --- /dev/null +++ b/WebKit/mac/Misc/WebNSAttributedStringExtras.mm @@ -0,0 +1,556 @@ +/* + * Copyright (C) 2005, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "WebNSAttributedStringExtras.h" + +#import "DOMRangeInternal.h" +#import "WebDataSourcePrivate.h" +#import "WebFrame.h" +#import "WebFrameBridge.h" +#import "WebFrameInternal.h" +#import <WebCore/BlockExceptions.h> +#import <WebCore/ColorMac.h> +#import <WebCore/CSSHelper.h> +#import <WebCore/Document.h> +#import <WebCore/Element.h> +#import <WebCore/Frame.h> +#import <WebCore/FrameLoader.h> +#import <WebCore/HTMLNames.h> +#import <WebCore/Image.h> +#import <WebCore/InlineTextBox.h> +#import <WebCore/KURL.h> +#import <WebCore/Range.h> +#import <WebCore/RenderImage.h> +#import <WebCore/RenderListItem.h> +#import <WebCore/RenderObject.h> +#import <WebCore/RenderStyle.h> +#import <WebCore/RenderText.h> +#import <WebCore/SimpleFontData.h> +#import <WebCore/Text.h> + +using namespace WebCore; +using namespace HTMLNames; + +struct ListItemInfo { + unsigned start; + unsigned end; +}; + +static Element* listParent(Element* item) +{ + while (!item->hasTagName(ulTag) && !item->hasTagName(olTag)) { + item = static_cast<Element*>(item->parentNode()); + if (!item) + break; + } + return item; +} + +static Node* isTextFirstInListItem(Node* e) +{ + if (!e->isTextNode()) + return 0; + Node* par = e->parentNode(); + while (par) { + if (par->firstChild() != e) + return 0; + if (par->hasTagName(liTag)) + return par; + e = par; + par = par->parentNode(); + } + return 0; +} + +static NSFileWrapper *fileWrapperForElement(Element* e) +{ + NSFileWrapper *wrapper = nil; + BEGIN_BLOCK_OBJC_EXCEPTIONS; + + const AtomicString& attr = e->getAttribute(srcAttr); + if (!attr.isEmpty()) { + NSURL *URL = e->document()->completeURL(attr); + wrapper = [[kit(e->document()->frame()) _dataSource] _fileWrapperForURL:URL]; + } + if (!wrapper) { + RenderImage* renderer = static_cast<RenderImage*>(e->renderer()); + if (renderer->cachedImage() && !renderer->cachedImage()->errorOccurred()) { + wrapper = [[NSFileWrapper alloc] initRegularFileWithContents:(NSData *)(renderer->cachedImage()->image()->getTIFFRepresentation())]; + [wrapper setPreferredFilename:@"image.tiff"]; + [wrapper autorelease]; + } + } + + return wrapper; + + END_BLOCK_OBJC_EXCEPTIONS; + + return nil; +} + +@implementation NSAttributedString (WebKitExtras) + +- (NSAttributedString *)_web_attributedStringByStrippingAttachmentCharacters +{ + // This code was originally copied from NSTextView + NSRange attachmentRange; + NSString *originalString = [self string]; + static NSString *attachmentCharString = nil; + + if (!attachmentCharString) { + unichar chars[2]; + if (!attachmentCharString) { + chars[0] = NSAttachmentCharacter; + chars[1] = 0; + attachmentCharString = [[NSString alloc] initWithCharacters:chars length:1]; + } + } + + attachmentRange = [originalString rangeOfString:attachmentCharString]; + if (attachmentRange.location != NSNotFound && attachmentRange.length > 0) { + NSMutableAttributedString *newAttributedString = [[self mutableCopyWithZone:NULL] autorelease]; + + while (attachmentRange.location != NSNotFound && attachmentRange.length > 0) { + [newAttributedString replaceCharactersInRange:attachmentRange withString:@""]; + attachmentRange = [[newAttributedString string] rangeOfString:attachmentCharString]; + } + return newAttributedString; + } + + return self; +} + +// FIXME: Use WebCore::TextIterator to iterate text runs. + ++ (NSAttributedString *)_web_attributedStringFromRange:(Range*)range +{ + ListItemInfo info; + ExceptionCode ec = 0; // dummy variable -- we ignore DOM exceptions + NSMutableAttributedString *result; + BEGIN_BLOCK_OBJC_EXCEPTIONS; + + if (!range || !range->boundaryPointsValid()) + return nil; + + Node* firstNode = range->startNode(); + if (!firstNode) + return nil; + Node* pastEndNode = range->pastEndNode(); + + int startOffset = range->startOffset(ec); + int endOffset = range->endOffset(ec); + Node* endNode = range->endContainer(ec); + + result = [[[NSMutableAttributedString alloc] init] autorelease]; + + bool hasNewLine = true; + bool addedSpace = true; + NSAttributedString *pendingStyledSpace = nil; + bool hasParagraphBreak = true; + const Element *linkStartNode = 0; + unsigned linkStartLocation = 0; + Vector<Element*> listItems; + Vector<ListItemInfo> listItemLocations; + float maxMarkerWidth = 0; + + Node *currentNode = firstNode; + + // If the first item is the entire text of a list item, use the list item node as the start of the + // selection, not the text node. The user's intent was probably to select the list. + if (currentNode->isTextNode() && startOffset == 0) { + Node *startListNode = isTextFirstInListItem(firstNode); + if (startListNode){ + firstNode = startListNode; + currentNode = firstNode; + } + } + + while (currentNode && currentNode != pastEndNode) { + RenderObject *renderer = currentNode->renderer(); + if (renderer) { + RenderStyle *style = renderer->style(); + NSFont *font = style->font().primaryFont()->getNSFont(); + bool needSpace = pendingStyledSpace != nil; + if (currentNode->isTextNode()) { + if (hasNewLine) { + addedSpace = true; + needSpace = false; + [pendingStyledSpace release]; + pendingStyledSpace = nil; + hasNewLine = false; + } + String text; + String str = currentNode->nodeValue(); + int start = (currentNode == firstNode) ? startOffset : -1; + int end = (currentNode == endNode) ? endOffset : -1; + if (renderer->isText()) { + if (!style->collapseWhiteSpace()) { + if (needSpace && !addedSpace) { + if (text.isEmpty() && linkStartLocation == [result length]) + ++linkStartLocation; + [result appendAttributedString:pendingStyledSpace]; + } + int runStart = (start == -1) ? 0 : start; + int runEnd = (end == -1) ? str.length() : end; + text += str.substring(runStart, runEnd-runStart); + [pendingStyledSpace release]; + pendingStyledSpace = nil; + addedSpace = u_charDirection(str.characters()[runEnd - 1]) == U_WHITE_SPACE_NEUTRAL; + } + else { + RenderText* textObj = static_cast<RenderText*>(renderer); + if (!textObj->firstTextBox() && str.length() > 0 && !addedSpace) { + // We have no runs, but we do have a length. This means we must be + // whitespace that collapsed away at the end of a line. + text.append(' '); + addedSpace = true; + } + else { + addedSpace = false; + for (InlineTextBox* box = textObj->firstTextBox(); box; box = box->nextTextBox()) { + int runStart = (start == -1) ? box->m_start : start; + int runEnd = (end == -1) ? box->m_start + box->m_len : end; + if (runEnd > box->m_start + box->m_len) + runEnd = box->m_start + box->m_len; + if (runStart >= box->m_start && + runStart < box->m_start + box->m_len) { + if (box == textObj->firstTextBox() && box->m_start == runStart && runStart > 0) + needSpace = true; // collapsed space at the start + if (needSpace && !addedSpace) { + if (pendingStyledSpace != nil) { + if (text.isEmpty() && linkStartLocation == [result length]) + ++linkStartLocation; + [result appendAttributedString:pendingStyledSpace]; + } else + text.append(' '); + } + String runText = str.substring(runStart, runEnd - runStart); + runText.replace('\n', ' '); + text += runText; + int nextRunStart = box->nextTextBox() ? box->nextTextBox()->m_start : str.length(); // collapsed space between runs or at the end + needSpace = nextRunStart > runEnd; + [pendingStyledSpace release]; + pendingStyledSpace = nil; + addedSpace = u_charDirection(str.characters()[runEnd - 1]) == U_WHITE_SPACE_NEUTRAL; + start = -1; + } + if (end != -1 && runEnd >= end) + break; + } + } + } + } + + text.replace('\\', renderer->backslashAsCurrencySymbol()); + + if (text.length() > 0 || needSpace) { + NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init]; + [attrs setObject:font forKey:NSFontAttributeName]; + if (style && style->color().isValid() && style->color().alpha() != 0) + [attrs setObject:nsColor(style->color()) forKey:NSForegroundColorAttributeName]; + if (style && style->backgroundColor().isValid() && style->backgroundColor().alpha() != 0) + [attrs setObject:nsColor(style->backgroundColor()) forKey:NSBackgroundColorAttributeName]; + + if (text.length() > 0) { + hasParagraphBreak = false; + NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text attributes:attrs]; + [result appendAttributedString: partialString]; + [partialString release]; + } + + if (needSpace) { + [pendingStyledSpace release]; + pendingStyledSpace = [[NSAttributedString alloc] initWithString:@" " attributes:attrs]; + } + + [attrs release]; + } + } else { + // This is our simple HTML -> ASCII transformation: + String text; + if (currentNode->hasTagName(aTag)) { + // Note the start of the <a> element. We will add the NSLinkAttributeName + // attribute to the attributed string when navigating to the next sibling + // of this node. + linkStartLocation = [result length]; + linkStartNode = static_cast<Element*>(currentNode); + } else if (currentNode->hasTagName(brTag)) { + text.append('\n'); + hasNewLine = true; + } else if (currentNode->hasTagName(liTag)) { + String listText; + Element *itemParent = listParent(static_cast<Element*>(currentNode)); + + if (!hasNewLine) + listText.append('\n'); + hasNewLine = true; + + listItems.append(static_cast<Element*>(currentNode)); + info.start = [result length]; + info.end = 0; + listItemLocations.append (info); + + listText.append('\t'); + if (itemParent && renderer->isListItem()) { + RenderListItem* listRenderer = static_cast<RenderListItem*>(renderer); + + maxMarkerWidth = MAX([font pointSize], maxMarkerWidth); + + String marker = listRenderer->markerText(); + if (!marker.isEmpty()) { + listText.append(marker); + // Use AppKit metrics, since this will be rendered by AppKit. + NSString *markerNSString = marker; + float markerWidth = [markerNSString sizeWithAttributes:[NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]].width; + maxMarkerWidth = MAX(markerWidth, maxMarkerWidth); + } + + listText.append(' '); + listText.append('\t'); + + NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init]; + [attrs setObject:font forKey:NSFontAttributeName]; + if (style && style->color().isValid()) + [attrs setObject:nsColor(style->color()) forKey:NSForegroundColorAttributeName]; + if (style && style->backgroundColor().isValid()) + [attrs setObject:nsColor(style->backgroundColor()) forKey:NSBackgroundColorAttributeName]; + + NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:listText attributes:attrs]; + [attrs release]; + [result appendAttributedString: partialString]; + [partialString release]; + } + } else if (currentNode->hasTagName(olTag) || currentNode->hasTagName(ulTag)) { + if (!hasNewLine) + text.append('\n'); + hasNewLine = true; + } else if (currentNode->hasTagName(blockquoteTag) + || currentNode->hasTagName(ddTag) + || currentNode->hasTagName(divTag) + || currentNode->hasTagName(dlTag) + || currentNode->hasTagName(dtTag) + || currentNode->hasTagName(hrTag) + || currentNode->hasTagName(listingTag) + || currentNode->hasTagName(preTag) + || currentNode->hasTagName(tdTag) + || currentNode->hasTagName(thTag)) { + if (!hasNewLine) + text.append('\n'); + hasNewLine = true; + } else if (currentNode->hasTagName(h1Tag) + || currentNode->hasTagName(h2Tag) + || currentNode->hasTagName(h3Tag) + || currentNode->hasTagName(h4Tag) + || currentNode->hasTagName(h5Tag) + || currentNode->hasTagName(h6Tag) + || currentNode->hasTagName(pTag) + || currentNode->hasTagName(trTag)) { + if (!hasNewLine) + text.append('\n'); + + // In certain cases, emit a paragraph break. + int bottomMargin = renderer->collapsedMarginBottom(); + int fontSize = style->fontDescription().computedPixelSize(); + if (bottomMargin * 2 >= fontSize) { + if (!hasParagraphBreak) { + text.append('\n'); + hasParagraphBreak = true; + } + } + + hasNewLine = true; + } else if (currentNode->hasTagName(imgTag)) { + if (pendingStyledSpace != nil) { + if (linkStartLocation == [result length]) + ++linkStartLocation; + [result appendAttributedString:pendingStyledSpace]; + [pendingStyledSpace release]; + pendingStyledSpace = nil; + } + NSFileWrapper *fileWrapper = fileWrapperForElement(static_cast<Element*>(currentNode)); + NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper]; + NSAttributedString *iString = [NSAttributedString attributedStringWithAttachment:attachment]; + [result appendAttributedString: iString]; + [attachment release]; + } + + NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text]; + [result appendAttributedString: partialString]; + [partialString release]; + } + } + + Node* nextNode = currentNode->firstChild(); + if (!nextNode) + nextNode = currentNode->nextSibling(); + + while (!nextNode && currentNode->parentNode()) { + String text; + currentNode = currentNode->parentNode(); + if (currentNode == pastEndNode) + break; + nextNode = currentNode->nextSibling(); + + if (currentNode->hasTagName(aTag)) { + // End of a <a> element. Create an attributed string NSLinkAttributeName attribute + // for the range of the link. Note that we create the attributed string from the DOM, which + // will have corrected any illegally nested <a> elements. + if (linkStartNode && currentNode == linkStartNode) { + NSURL *URL = linkStartNode->document()->frame()->loader()->completeURL(parseURL(linkStartNode->getAttribute(hrefAttr))); + NSRange tempRange = { linkStartLocation, [result length]-linkStartLocation }; // workaround for 4213314 + [result addAttribute:NSLinkAttributeName value:URL range:tempRange]; + linkStartNode = 0; + } + } else if (currentNode->hasTagName(olTag) || currentNode->hasTagName(ulTag)) { + if (!hasNewLine) + text.append('\n'); + hasNewLine = true; + } else if (currentNode->hasTagName(liTag)) { + + int i, count = listItems.size(); + for (i = 0; i < count; i++){ + if (listItems[i] == currentNode){ + listItemLocations[i].end = [result length]; + break; + } + } + if (!hasNewLine) + text.append('\n'); + hasNewLine = true; + } else if (currentNode->hasTagName(blockquoteTag) || + currentNode->hasTagName(ddTag) || + currentNode->hasTagName(divTag) || + currentNode->hasTagName(dlTag) || + currentNode->hasTagName(dtTag) || + currentNode->hasTagName(hrTag) || + currentNode->hasTagName(listingTag) || + currentNode->hasTagName(preTag) || + currentNode->hasTagName(tdTag) || + currentNode->hasTagName(thTag)) { + if (!hasNewLine) + text.append('\n'); + hasNewLine = true; + } else if (currentNode->hasTagName(pTag) || + currentNode->hasTagName(trTag) || + currentNode->hasTagName(h1Tag) || + currentNode->hasTagName(h2Tag) || + currentNode->hasTagName(h3Tag) || + currentNode->hasTagName(h4Tag) || + currentNode->hasTagName(h5Tag) || + currentNode->hasTagName(h6Tag)) { + if (!hasNewLine) + text.append('\n'); + // An extra newline is needed at the start, not the end, of these types of tags, + // so don't add another here. + hasNewLine = true; + } + + NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text]; + [result appendAttributedString:partialString]; + [partialString release]; + } + + currentNode = nextNode; + } + + [pendingStyledSpace release]; + + // Apply paragraph styles from outside in. This ensures that nested lists correctly + // override their parent's paragraph style. + { + unsigned i, count = listItems.size(); + Element *e; + +#ifdef POSITION_LIST + Node *containingBlock; + int containingBlockX, containingBlockY; + + // Determine the position of the outermost containing block. All paragraph + // styles and tabs should be relative to this position. So, the horizontal position of + // each item in the list (in the resulting attributed string) will be relative to position + // of the outermost containing block. + if (count > 0){ + containingBlock = firstNode; + while (containingBlock->renderer()->isInline()){ + containingBlock = containingBlock->parentNode(); + } + containingBlock->renderer()->absolutePosition(containingBlockX, containingBlockY); + } +#endif + + for (i = 0; i < count; i++){ + e = listItems[i]; + info = listItemLocations[i]; + + if (info.end < info.start) + info.end = [result length]; + + RenderObject *r = e->renderer(); + RenderStyle *style = r->style(); + + int rx; + NSFont *font = style->font().primaryFont()->getNSFont(); + float pointSize = [font pointSize]; + +#ifdef POSITION_LIST + int ry; + r->absolutePosition(rx, ry); + rx -= containingBlockX; + + // Ensure that the text is indented at least enough to allow for the markers. + rx = MAX(rx, (int)maxMarkerWidth); +#else + rx = (int)MAX(maxMarkerWidth, pointSize); +#endif + + // The bullet text will be right aligned at the first tab marker, followed + // by a space, followed by the list item text. The space is arbitrarily + // picked as pointSize*2/3. The space on the first line of the text item + // is established by a left aligned tab, on subsequent lines it's established + // by the head indent. + NSMutableParagraphStyle *mps = [[NSMutableParagraphStyle alloc] init]; + [mps setFirstLineHeadIndent: 0]; + [mps setHeadIndent: rx]; + [mps setTabStops:[NSArray arrayWithObjects: + [[[NSTextTab alloc] initWithType:NSRightTabStopType location:rx-(pointSize*2/3)] autorelease], + [[[NSTextTab alloc] initWithType:NSLeftTabStopType location:rx] autorelease], + nil]]; + NSRange tempRange = { info.start, info.end-info.start }; // workaround for 4213314 + [result addAttribute:NSParagraphStyleAttributeName value:mps range:tempRange]; + [mps release]; + } + } + + return result; + + END_BLOCK_OBJC_EXCEPTIONS; + + return nil; +} + +@end |