1 /*
2 * Copyright 2003-2008, Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Stefano Ceccherini (stefano.ceccherini@gmail.com)
7 */
8
9 //! Caches string widths in a hash table, to avoid a trip to the app server.
10
11
12 #include "utf8_functions.h"
13 #include "TextGapBuffer.h"
14 #include "WidthBuffer.h"
15
16 #include <Autolock.h>
17 #include <Debug.h>
18 #include <Font.h>
19 #include <Locker.h>
20
21 #include <stdio.h>
22
23
24 //! NetPositive binary compatibility support
25 class _BWidthBuffer_;
26
27
28 namespace BPrivate {
29
30
31 const static uint32 kTableCount = 128;
32 const static uint32 kInvalidCode = 0xFFFFFFFF;
33 WidthBuffer* gWidthBuffer = NULL;
34 // initialized in InterfaceDefs.cpp
35
36
37 struct hashed_escapement {
38 uint32 code;
39 float escapement;
40
hashed_escapementBPrivate::hashed_escapement41 hashed_escapement()
42 {
43 code = kInvalidCode;
44 escapement = 0;
45 }
46 };
47
48
49 /*! \brief Convert a UTF8 char to a code, which will be used
50 to uniquely identify the character in the hash table.
51 \param text A pointer to the character to examine.
52 \param charLen the length of the character to examine.
53 \return The code for the given character,
54 */
55 static inline uint32
CharToCode(const char * text,const int32 charLen)56 CharToCode(const char* text, const int32 charLen)
57 {
58 uint32 value = 0;
59 int32 shiftVal = 24;
60 for (int32 c = 0; c < charLen; c++) {
61 value |= ((unsigned char)text[c] << shiftVal);
62 shiftVal -= 8;
63 }
64 return value;
65 }
66
67
68 /*! \brief Initializes the object.
69 */
WidthBuffer()70 WidthBuffer::WidthBuffer()
71 :
72 _BTextViewSupportBuffer_<_width_table_>(1, 0),
73 fLock("width buffer")
74 {
75 }
76
77
78 /*! \brief Frees the allocated resources.
79 */
~WidthBuffer()80 WidthBuffer::~WidthBuffer()
81 {
82 for (int32 x = 0; x < fItemCount; x++)
83 delete[] (hashed_escapement*)fBuffer[x].widths;
84 }
85
86
87 /*! \brief Returns how much room is required to draw a string in the font.
88 \param inText The string to be examined.
89 \param fromOffset The offset in the string where to begin the examination.
90 \param lenght The amount of bytes to be examined.
91 \param inStyle The font.
92 \return The space (in pixels) required to draw the given string.
93 */
94 float
StringWidth(const char * inText,int32 fromOffset,int32 length,const BFont * inStyle)95 WidthBuffer::StringWidth(const char* inText, int32 fromOffset, int32 length,
96 const BFont* inStyle)
97 {
98 if (inText == NULL || length <= 0)
99 return 0;
100
101 BAutolock _(fLock);
102
103 int32 index = 0;
104 if (!FindTable(inStyle, &index))
105 index = InsertTable(inStyle);
106
107 char* text = NULL;
108 int32 numChars = 0;
109 int32 textLen = 0;
110
111 const char* sourceText = inText + fromOffset;
112 const float fontSize = inStyle->Size();
113 float stringWidth = 0;
114
115 for (int32 charLen = 0; length > 0;
116 sourceText += charLen, length -= charLen) {
117 charLen = UTF8NextCharLen(sourceText, length);
118
119 // End of string, bail out
120 if (charLen <= 0)
121 break;
122
123 // Some magic, to uniquely identify this character
124 const uint32 value = CharToCode(sourceText, charLen);
125
126 float escapement;
127 if (GetEscapement(value, index, &escapement)) {
128 // Well, we've got a match for this character
129 stringWidth += escapement;
130 } else {
131 // Store this character into an array, which we'll
132 // pass to HashEscapements() later
133 int32 offset = textLen;
134 textLen += charLen;
135 numChars++;
136 char* newText = (char*)realloc(text, textLen + 1);
137 if (newText == NULL) {
138 free(text);
139 return 0;
140 }
141
142 text = newText;
143 memcpy(&text[offset], sourceText, charLen);
144 }
145 }
146
147 if (text != NULL) {
148 // We've found some characters which aren't yet in the hash table.
149 // Get their width via HashEscapements()
150 text[textLen] = 0;
151 stringWidth += HashEscapements(text, numChars, textLen, index, inStyle);
152 free(text);
153 }
154
155 return stringWidth * fontSize;
156 }
157
158
159 /*! \brief Returns how much room is required to draw a string in the font.
160 \param inBuffer The TextGapBuffer to be examined.
161 \param fromOffset The offset in the TextGapBuffer where to begin the
162 examination.
163 \param lenght The amount of bytes to be examined.
164 \param inStyle The font.
165 \return The space (in pixels) required to draw the given string.
166 */
167 float
StringWidth(TextGapBuffer & inBuffer,int32 fromOffset,int32 length,const BFont * inStyle)168 WidthBuffer::StringWidth(TextGapBuffer &inBuffer, int32 fromOffset,
169 int32 length, const BFont* inStyle)
170 {
171 const char* text = inBuffer.GetString(fromOffset, &length);
172 return StringWidth(text, 0, length, inStyle);
173 }
174
175
176 /*! \brief Searches for the table for the given font.
177 \param inStyle The font to search for.
178 \param outIndex a pointer to an int32, where the function will store
179 the index of the table, if found, or -1, if not.
180 \return \c true if the function founds the table,
181 \c false if not.
182 */
183 bool
FindTable(const BFont * inStyle,int32 * outIndex)184 WidthBuffer::FindTable(const BFont* inStyle, int32* outIndex)
185 {
186 if (inStyle == NULL)
187 return false;
188
189 int32 tableIndex = -1;
190
191 for (int32 i = 0; i < fItemCount; i++) {
192 if (*inStyle == fBuffer[i].font) {
193 tableIndex = i;
194 break;
195 }
196 }
197 if (outIndex != NULL)
198 *outIndex = tableIndex;
199
200 return tableIndex != -1;
201 }
202
203
204 /*! \brief Creates and insert an empty table for the given font.
205 \param font The font to create the table for.
206 \return The index of the newly created table.
207 */
208 int32
InsertTable(const BFont * font)209 WidthBuffer::InsertTable(const BFont* font)
210 {
211 _width_table_ table;
212
213 table.font = *font;
214 table.hashCount = 0;
215 table.tableCount = kTableCount;
216 table.widths = new hashed_escapement[kTableCount];
217
218 uint32 position = fItemCount;
219 InsertItemsAt(1, position, &table);
220
221 return position;
222 }
223
224
225 /*! \brief Gets the escapement for the given character.
226 \param value An integer which uniquely identifies a character.
227 \param index The index of the table to search.
228 \param escapement A pointer to a float, where the function will
229 store the escapement.
230 \return \c true if the function could find the escapement
231 for the given character, \c false if not.
232 */
233 bool
GetEscapement(uint32 value,int32 index,float * escapement)234 WidthBuffer::GetEscapement(uint32 value, int32 index, float* escapement)
235 {
236 const _width_table_ &table = fBuffer[index];
237 const hashed_escapement* widths
238 = static_cast<hashed_escapement*>(table.widths);
239 uint32 hashed = Hash(value) & (table.tableCount - 1);
240
241 uint32 found;
242 while ((found = widths[hashed].code) != kInvalidCode) {
243 if (found == value)
244 break;
245
246 if (++hashed >= (uint32)table.tableCount)
247 hashed = 0;
248 }
249
250 if (found == kInvalidCode)
251 return false;
252
253 if (escapement != NULL)
254 *escapement = widths[hashed].escapement;
255
256 return true;
257 }
258
259
260 uint32
Hash(uint32 val)261 WidthBuffer::Hash(uint32 val)
262 {
263 uint32 shifted = val >> 24;
264 uint32 result = (val >> 15) + (shifted * 3);
265
266 result ^= (val >> 6) - (shifted * 22);
267 result ^= (val << 3);
268
269 return result;
270 }
271
272
273 /*! \brief Gets the escapements for the given string, and put them into
274 the hash table.
275 \param inText The string to be examined.
276 \param numChars The amount of characters contained in the string.
277 \param textLen the amount of bytes contained in the string.
278 \param tableIndex the index of the table where the escapements
279 should be put.
280 \param inStyle the font.
281 \return The width of the supplied string (which should be multiplied by
282 the size of the font).
283 */
284 float
HashEscapements(const char * inText,int32 numChars,int32 textLen,int32 tableIndex,const BFont * inStyle)285 WidthBuffer::HashEscapements(const char* inText, int32 numChars, int32 textLen,
286 int32 tableIndex, const BFont* inStyle)
287 {
288 ASSERT(inText != NULL);
289 ASSERT(numChars > 0);
290 ASSERT(textLen > 0);
291
292 float* escapements = new float[numChars];
293 inStyle->GetEscapements(inText, numChars, escapements);
294
295 _width_table_ &table = fBuffer[tableIndex];
296 hashed_escapement* widths = static_cast<hashed_escapement*>(table.widths);
297
298 int32 charCount = 0;
299 char* text = (char*)inText;
300 const char* textEnd = inText + textLen;
301 // Insert the escapements into the hash table
302 do {
303 // Using this variant is safe as the handed in string is guaranteed to
304 // be 0 terminated.
305 const int32 charLen = UTF8NextCharLen(text);
306 if (charLen == 0)
307 break;
308
309 const uint32 value = CharToCode(text, charLen);
310
311 uint32 hashed = Hash(value) & (table.tableCount - 1);
312 uint32 found;
313 while ((found = widths[hashed].code) != kInvalidCode) {
314 if (found == value)
315 break;
316 if (++hashed >= (uint32)table.tableCount)
317 hashed = 0;
318 }
319
320 if (found == kInvalidCode) {
321 // The value is not in the table. Add it.
322 widths[hashed].code = value;
323 widths[hashed].escapement = escapements[charCount];
324 table.hashCount++;
325
326 // We always keep some free space in the hash table:
327 // we double the current size when hashCount is 2/3 of
328 // the total size.
329 if (table.tableCount * 2 / 3 <= table.hashCount) {
330 const int32 newSize = table.tableCount * 2;
331
332 // Create and initialize a new hash table
333 hashed_escapement* newWidths = new hashed_escapement[newSize];
334
335 // Rehash the values, and put them into the new table
336 for (uint32 oldPos = 0; oldPos < (uint32)table.tableCount;
337 oldPos++) {
338 if (widths[oldPos].code != kInvalidCode) {
339 uint32 newPos
340 = Hash(widths[oldPos].code) & (newSize - 1);
341 while (newWidths[newPos].code != kInvalidCode) {
342 if (++newPos >= (uint32)newSize)
343 newPos = 0;
344 }
345 newWidths[newPos] = widths[oldPos];
346 }
347 }
348
349 // Delete the old table, and put the new pointer into the
350 // _width_table_
351 delete[] widths;
352 table.tableCount = newSize;
353 table.widths = widths = newWidths;
354 }
355 }
356 charCount++;
357 text += charLen;
358 } while (text < textEnd);
359
360 // Calculate the width of the string
361 float width = 0;
362 for (int32 x = 0; x < numChars; x++)
363 width += escapements[x];
364
365 delete[] escapements;
366
367 return width;
368 }
369
370 } // namespace BPrivate
371
372
373 #if __GNUC__ < 3
374 //! NetPositive binary compatibility support
375
_BWidthBuffer_()376 _BWidthBuffer_::_BWidthBuffer_()
377 {
378 }
379
~_BWidthBuffer_()380 _BWidthBuffer_::~_BWidthBuffer_()
381 {
382 }
383
384 _BWidthBuffer_* gCompatibilityWidthBuffer = NULL;
385
386 extern "C"
387 float
StringWidth__14_BWidthBuffer_PCcllPC5BFont(_BWidthBuffer_ * widthBuffer,const char * inText,int32 fromOffset,int32 length,const BFont * inStyle)388 StringWidth__14_BWidthBuffer_PCcllPC5BFont(_BWidthBuffer_* widthBuffer,
389 const char* inText, int32 fromOffset, int32 length, const BFont* inStyle)
390 {
391 return BPrivate::gWidthBuffer->StringWidth(inText, fromOffset, length,
392 inStyle);
393 }
394
395 #endif // __GNUC__ < 3
396
397