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