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
39 namespace WebCore {
40
41 #define STRING_BUFFER_SIZE 2048
42
43 typedef unsigned TruncationFunction(const String&, unsigned length, unsigned keepCount, UChar* buffer);
44
textBreakAtOrPreceding(TextBreakIterator * it,int offset)45 static 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
boundedTextBreakFollowing(TextBreakIterator * it,int offset,int length)54 static inline int boundedTextBreakFollowing(TextBreakIterator* it, int offset, int length)
55 {
56 int result = textBreakFollowing(it, offset);
57 return result == TextBreakDone ? length : result;
58 }
59
centerTruncateToBuffer(const String & string,unsigned length,unsigned keepCount,UChar * buffer)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 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
rightTruncateToBuffer(const String & string,unsigned length,unsigned keepCount,UChar * buffer)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 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
stringWidth(const Font & renderer,const UChar * characters,unsigned length)95 static float stringWidth(const Font& renderer, const UChar* characters, unsigned length)
96 {
97 TextRun run(characters, length);
98 return renderer.width(run);
99 }
100
truncateString(const String & string,float maxWidth,const Font & font,TruncationFunction truncateToBuffer)101 static 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
centerTruncate(const String & string,float maxWidth,const Font & font)182 String StringTruncator::centerTruncate(const String& string, float maxWidth, const Font& font)
183 {
184 return truncateString(string, maxWidth, font, centerTruncateToBuffer);
185 }
186
rightTruncate(const String & string,float maxWidth,const Font & font)187 String StringTruncator::rightTruncate(const String& string, float maxWidth, const Font& font)
188 {
189 return truncateString(string, maxWidth, font, rightTruncateToBuffer);
190 }
191
width(const String & string,const Font & font)192 float StringTruncator::width(const String& string, const Font& font)
193 {
194 return stringWidth(font, string.characters(), string.length());
195 }
196
197 } // namespace WebCore
198