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)
97 TextRun run(characters, length);
98 return renderer.width(run);
101 static String truncateString(const String& string, float maxWidth, const Font& font, TruncationFunction truncateToBuffer)
103 if (string.isEmpty())
106 ASSERT(maxWidth >= 0);
108 float currentEllipsisWidth = stringWidth(font, &horizontalEllipsis, 1);
110 UChar stringBuffer[STRING_BUFFER_SIZE];
111 unsigned truncatedLength;
113 unsigned length = string.length();
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);
120 memcpy(stringBuffer, string.characters(), sizeof(UChar) * length);
121 truncatedLength = length;
124 float width = stringWidth(font, stringBuffer, truncatedLength);
125 if (width <= maxWidth)
128 unsigned keepCountForLargestKnownToFit = 0;
129 float widthForLargestKnownToFit = currentEllipsisWidth;
131 unsigned keepCountForSmallestKnownToNotFit = keepCount;
132 float widthForSmallestKnownToNotFit = width;
134 if (currentEllipsisWidth >= maxWidth) {
135 keepCountForLargestKnownToFit = 1;
136 keepCountForSmallestKnownToNotFit = 2;
139 while (keepCountForLargestKnownToFit + 1 < keepCountForSmallestKnownToNotFit) {
140 ASSERT(widthForLargestKnownToFit <= maxWidth);
141 ASSERT(widthForSmallestKnownToNotFit > maxWidth);
143 float ratio = (keepCountForSmallestKnownToNotFit - keepCountForLargestKnownToFit)
144 / (widthForSmallestKnownToNotFit - widthForLargestKnownToFit);
145 keepCount = static_cast<unsigned>(maxWidth * ratio);
147 if (keepCount <= keepCountForLargestKnownToFit) {
148 keepCount = keepCountForLargestKnownToFit + 1;
149 } else if (keepCount >= keepCountForSmallestKnownToNotFit) {
150 keepCount = keepCountForSmallestKnownToNotFit - 1;
153 ASSERT(keepCount < length);
154 ASSERT(keepCount > 0);
155 ASSERT(keepCount < keepCountForSmallestKnownToNotFit);
156 ASSERT(keepCount > keepCountForLargestKnownToFit);
158 truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer);
160 width = stringWidth(font, stringBuffer, truncatedLength);
161 if (width <= maxWidth) {
162 keepCountForLargestKnownToFit = keepCount;
163 widthForLargestKnownToFit = width;
165 keepCountForSmallestKnownToNotFit = keepCount;
166 widthForSmallestKnownToNotFit = width;
170 if (keepCountForLargestKnownToFit == 0) {
171 keepCountForLargestKnownToFit = 1;
174 if (keepCount != keepCountForLargestKnownToFit) {
175 keepCount = keepCountForLargestKnownToFit;
176 truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer);
179 return String(stringBuffer, truncatedLength);
182 String StringTruncator::centerTruncate(const String& string, float maxWidth, const Font& font)
184 return truncateString(string, maxWidth, font, centerTruncateToBuffer);
187 String StringTruncator::rightTruncate(const String& string, float maxWidth, const Font& font)
189 return truncateString(string, maxWidth, font, rightTruncateToBuffer);
192 float StringTruncator::width(const String& string, const Font& font)
194 return stringWidth(font, string.characters(), string.length());
197 } // namespace WebCore