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