xref: /haiku/src/kits/interface/textview_support/WidthBuffer.cpp (revision 25a7b01d15612846f332751841da3579db313082)
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