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
25class _BWidthBuffer_;
26
27
28namespace BPrivate {
29
30
31const static uint32 kTableCount = 128;
32const static uint32 kInvalidCode = 0xFFFFFFFF;
33WidthBuffer* gWidthBuffer = NULL;
34	// initialized in InterfaceDefs.cpp
35
36
37struct hashed_escapement {
38	uint32 code;
39	float escapement;
40
41	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*/
55static inline uint32
56CharToCode(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*/
70WidthBuffer::WidthBuffer()
71	:
72	_BTextViewSupportBuffer_<_width_table_>(1, 0),
73	fLock("width buffer")
74{
75}
76
77
78/*! \brief Frees the allocated resources.
79*/
80WidthBuffer::~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*/
94float
95WidthBuffer::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*/
167float
168WidthBuffer::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*/
183bool
184WidthBuffer::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*/
208int32
209WidthBuffer::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*/
233bool
234WidthBuffer::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
260uint32
261WidthBuffer::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*/
284float
285WidthBuffer::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
376_BWidthBuffer_::_BWidthBuffer_()
377{
378}
379
380_BWidthBuffer_::~_BWidthBuffer_()
381{
382}
383
384_BWidthBuffer_* gCompatibilityWidthBuffer = NULL;
385
386extern "C"
387float
388StringWidth__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