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