1/* 2 * Copyright (C) 2005, 2006, 2007 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 * 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#include "config.h" 30#include "StringTruncator.h" 31 32#include "Font.h" 33#include "TextBreakIterator.h" 34#include "TextRun.h" 35#include <wtf/Assertions.h> 36#include <wtf/Vector.h> 37#include <wtf/unicode/CharacterNames.h> 38 39namespace WebCore { 40 41#define STRING_BUFFER_SIZE 2048 42 43typedef unsigned TruncationFunction(const String&, unsigned length, unsigned keepCount, UChar* buffer); 44 45static inline int textBreakAtOrPreceding(TextBreakIterator* it, int offset) 46{ 47 if (isTextBreak(it, offset)) 48 return offset; 49 50 int result = textBreakPreceding(it, offset); 51 return result == TextBreakDone ? 0 : result; 52} 53 54static inline int boundedTextBreakFollowing(TextBreakIterator* it, int offset, int length) 55{ 56 int result = textBreakFollowing(it, offset); 57 return result == TextBreakDone ? length : result; 58} 59 60static unsigned centerTruncateToBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer) 61{ 62 ASSERT(keepCount < length); 63 ASSERT(keepCount < STRING_BUFFER_SIZE); 64 65 unsigned omitStart = (keepCount + 1) / 2; 66 TextBreakIterator* it = characterBreakIterator(string.characters(), length); 67 unsigned omitEnd = boundedTextBreakFollowing(it, omitStart + (length - keepCount) - 1, length); 68 omitStart = textBreakAtOrPreceding(it, omitStart); 69 70 unsigned truncatedLength = omitStart + 1 + (length - omitEnd); 71 ASSERT(truncatedLength <= length); 72 73 memcpy(buffer, string.characters(), sizeof(UChar) * omitStart); 74 buffer[omitStart] = horizontalEllipsis; 75 memcpy(&buffer[omitStart + 1], &string.characters()[omitEnd], sizeof(UChar) * (length - omitEnd)); 76 77 return truncatedLength; 78} 79 80static unsigned rightTruncateToBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer) 81{ 82 ASSERT(keepCount < length); 83 ASSERT(keepCount < STRING_BUFFER_SIZE); 84 85 TextBreakIterator* it = characterBreakIterator(string.characters(), length); 86 unsigned keepLength = textBreakAtOrPreceding(it, keepCount); 87 unsigned truncatedLength = keepLength + 1; 88 89 memcpy(buffer, string.characters(), sizeof(UChar) * keepLength); 90 buffer[keepLength] = horizontalEllipsis; 91 92 return truncatedLength; 93} 94 95static float stringWidth(const Font& renderer, const UChar* characters, unsigned length) 96{ 97 TextRun run(characters, length); 98 return renderer.width(run); 99} 100 101static String truncateString(const String& string, float maxWidth, const Font& font, TruncationFunction truncateToBuffer) 102{ 103 if (string.isEmpty()) 104 return string; 105 106 ASSERT(maxWidth >= 0); 107 108 float currentEllipsisWidth = stringWidth(font, &horizontalEllipsis, 1); 109 110 UChar stringBuffer[STRING_BUFFER_SIZE]; 111 unsigned truncatedLength; 112 unsigned keepCount; 113 unsigned length = string.length(); 114 115 if (length > STRING_BUFFER_SIZE) { 116 keepCount = STRING_BUFFER_SIZE - 1; // need 1 character for the ellipsis 117 truncatedLength = centerTruncateToBuffer(string, length, keepCount, stringBuffer); 118 } else { 119 keepCount = length; 120 memcpy(stringBuffer, string.characters(), sizeof(UChar) * length); 121 truncatedLength = length; 122 } 123 124 float width = stringWidth(font, stringBuffer, truncatedLength); 125 if (width <= maxWidth) 126 return string; 127 128 unsigned keepCountForLargestKnownToFit = 0; 129 float widthForLargestKnownToFit = currentEllipsisWidth; 130 131 unsigned keepCountForSmallestKnownToNotFit = keepCount; 132 float widthForSmallestKnownToNotFit = width; 133 134 if (currentEllipsisWidth >= maxWidth) { 135 keepCountForLargestKnownToFit = 1; 136 keepCountForSmallestKnownToNotFit = 2; 137 } 138 139 while (keepCountForLargestKnownToFit + 1 < keepCountForSmallestKnownToNotFit) { 140 ASSERT(widthForLargestKnownToFit <= maxWidth); 141 ASSERT(widthForSmallestKnownToNotFit > maxWidth); 142 143 float ratio = (keepCountForSmallestKnownToNotFit - keepCountForLargestKnownToFit) 144 / (widthForSmallestKnownToNotFit - widthForLargestKnownToFit); 145 keepCount = static_cast<unsigned>(maxWidth * ratio); 146 147 if (keepCount <= keepCountForLargestKnownToFit) { 148 keepCount = keepCountForLargestKnownToFit + 1; 149 } else if (keepCount >= keepCountForSmallestKnownToNotFit) { 150 keepCount = keepCountForSmallestKnownToNotFit - 1; 151 } 152 153 ASSERT(keepCount < length); 154 ASSERT(keepCount > 0); 155 ASSERT(keepCount < keepCountForSmallestKnownToNotFit); 156 ASSERT(keepCount > keepCountForLargestKnownToFit); 157 158 truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer); 159 160 width = stringWidth(font, stringBuffer, truncatedLength); 161 if (width <= maxWidth) { 162 keepCountForLargestKnownToFit = keepCount; 163 widthForLargestKnownToFit = width; 164 } else { 165 keepCountForSmallestKnownToNotFit = keepCount; 166 widthForSmallestKnownToNotFit = width; 167 } 168 } 169 170 if (keepCountForLargestKnownToFit == 0) { 171 keepCountForLargestKnownToFit = 1; 172 } 173 174 if (keepCount != keepCountForLargestKnownToFit) { 175 keepCount = keepCountForLargestKnownToFit; 176 truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer); 177 } 178 179 return String(stringBuffer, truncatedLength); 180} 181 182String StringTruncator::centerTruncate(const String& string, float maxWidth, const Font& font) 183{ 184 return truncateString(string, maxWidth, font, centerTruncateToBuffer); 185} 186 187String StringTruncator::rightTruncate(const String& string, float maxWidth, const Font& font) 188{ 189 return truncateString(string, maxWidth, font, rightTruncateToBuffer); 190} 191 192float StringTruncator::width(const String& string, const Font& font) 193{ 194 return stringWidth(font, string.characters(), string.length()); 195} 196 197} // namespace WebCore 198