2 * Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
30 #include "StringTruncator.h"
33 #include "TextBreakIterator.h"
35 #include <wtf/Assertions.h>
36 #include <wtf/Vector.h>
37 #include <wtf/unicode/CharacterNames.h>
41 #define STRING_BUFFER_SIZE 2048
43 typedef unsigned TruncationFunction(const String&, unsigned length, unsigned keepCount, UChar* buffer);
45 static inline int textBreakAtOrPreceding(TextBreakIterator* it, int offset)
47 if (isTextBreak(it, offset))
50 int result = textBreakPreceding(it, offset);
51 return result == TextBreakDone ? 0 : result;
54 static inline int boundedTextBreakFollowing(TextBreakIterator* it, int offset, int length)
56 int result = textBreakFollowing(it, offset);
57 return result == TextBreakDone ? length : result;
60 static unsigned centerTruncateToBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer)
62 ASSERT(keepCount < length);
63 ASSERT(keepCount < STRING_BUFFER_SIZE);
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);
70 unsigned truncatedLength = omitStart + 1 + (length - omitEnd);
71 ASSERT(truncatedLength <= length);
73 memcpy(buffer, string.characters(), sizeof(UChar) * omitStart);
74 buffer[omitStart] = horizontalEllipsis;
75 memcpy(&buffer[omitStart + 1], &string.characters()[omitEnd], sizeof(UChar) * (length - omitEnd));
77 return truncatedLength;
80 static unsigned rightTruncateToBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer)
82 ASSERT(keepCount < length);
83 ASSERT(keepCount < STRING_BUFFER_SIZE);
85 TextBreakIterator* it = characterBreakIterator(string.characters(), length);
86 unsigned keepLength = textBreakAtOrPreceding(it, keepCount);
87 unsigned truncatedLength = keepLength + 1;
89 memcpy(buffer, string.characters(), sizeof(UChar) * keepLength);
90 buffer[keepLength] = horizontalEllipsis;
92 return truncatedLength;
95 static float stringWidth(const Font& renderer, const UChar* characters, unsigned length, bool disableRoundingHacks)
97 TextRun run(characters, length);
98 if (disableRoundingHacks)
99 run.disableRoundingHacks();
100 return renderer.width(run);
103 static String truncateString(const String& string, float maxWidth, const Font& font, TruncationFunction truncateToBuffer, bool disableRoundingHacks)
105 if (string.isEmpty())
108 ASSERT(maxWidth >= 0);
110 float currentEllipsisWidth = stringWidth(font, &horizontalEllipsis, 1, disableRoundingHacks);
112 UChar stringBuffer[STRING_BUFFER_SIZE];
113 unsigned truncatedLength;
115 unsigned length = string.length();
117 if (length > STRING_BUFFER_SIZE) {
118 keepCount = STRING_BUFFER_SIZE - 1; // need 1 character for the ellipsis
119 truncatedLength = centerTruncateToBuffer(string, length, keepCount, stringBuffer);
122 memcpy(stringBuffer, string.characters(), sizeof(UChar) * length);
123 truncatedLength = length;
126 float width = stringWidth(font, stringBuffer, truncatedLength, disableRoundingHacks);
127 if (width <= maxWidth)
130 unsigned keepCountForLargestKnownToFit = 0;
131 float widthForLargestKnownToFit = currentEllipsisWidth;
133 unsigned keepCountForSmallestKnownToNotFit = keepCount;
134 float widthForSmallestKnownToNotFit = width;
136 if (currentEllipsisWidth >= maxWidth) {
137 keepCountForLargestKnownToFit = 1;
138 keepCountForSmallestKnownToNotFit = 2;
141 while (keepCountForLargestKnownToFit + 1 < keepCountForSmallestKnownToNotFit) {
142 ASSERT(widthForLargestKnownToFit <= maxWidth);
143 ASSERT(widthForSmallestKnownToNotFit > maxWidth);
145 float ratio = (keepCountForSmallestKnownToNotFit - keepCountForLargestKnownToFit)
146 / (widthForSmallestKnownToNotFit - widthForLargestKnownToFit);
147 keepCount = static_cast<unsigned>(maxWidth * ratio);
149 if (keepCount <= keepCountForLargestKnownToFit) {
150 keepCount = keepCountForLargestKnownToFit + 1;
151 } else if (keepCount >= keepCountForSmallestKnownToNotFit) {
152 keepCount = keepCountForSmallestKnownToNotFit - 1;
155 ASSERT(keepCount < length);
156 ASSERT(keepCount > 0);
157 ASSERT(keepCount < keepCountForSmallestKnownToNotFit);
158 ASSERT(keepCount > keepCountForLargestKnownToFit);
160 truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer);
162 width = stringWidth(font, stringBuffer, truncatedLength, disableRoundingHacks);
163 if (width <= maxWidth) {
164 keepCountForLargestKnownToFit = keepCount;
165 widthForLargestKnownToFit = width;
167 keepCountForSmallestKnownToNotFit = keepCount;
168 widthForSmallestKnownToNotFit = width;
172 if (keepCountForLargestKnownToFit == 0) {
173 keepCountForLargestKnownToFit = 1;
176 if (keepCount != keepCountForLargestKnownToFit) {
177 keepCount = keepCountForLargestKnownToFit;
178 truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer);
181 return String(stringBuffer, truncatedLength);
184 String StringTruncator::centerTruncate(const String& string, float maxWidth, const Font& font, EnableRoundingHacksOrNot enableRoundingHacks)
186 return truncateString(string, maxWidth, font, centerTruncateToBuffer, !enableRoundingHacks);
189 String StringTruncator::rightTruncate(const String& string, float maxWidth, const Font& font, EnableRoundingHacksOrNot enableRoundingHacks)
191 return truncateString(string, maxWidth, font, rightTruncateToBuffer, !enableRoundingHacks);
194 float StringTruncator::width(const String& string, const Font& font, EnableRoundingHacksOrNot enableRoundingHacks)
196 return stringWidth(font, string.characters(), string.length(), !enableRoundingHacks);
199 } // namespace WebCore