#include "utf8_functions.h"
#include "TextGapBuffer.h"
#include "WidthBuffer.h"
#include <Autolock.h>
#include <Debug.h>
#include <Font.h>
#include <Locker.h>
#include <stdio.h>
class _BWidthBuffer_;
namespace BPrivate {
const static uint32 kTableCount = 128;
const static uint32 kInvalidCode = 0xFFFFFFFF;
WidthBuffer* gWidthBuffer = NULL;
struct hashed_escapement {
uint32 code;
float escapement;
hashed_escapement()
{
code = kInvalidCode;
escapement = 0;
}
};
static inline uint32
CharToCode(const char* text, const int32 charLen)
{
uint32 value = 0;
int32 shiftVal = 24;
for (int32 c = 0; c < charLen; c++) {
value |= ((unsigned char)text[c] << shiftVal);
shiftVal -= 8;
}
return value;
}
WidthBuffer::WidthBuffer()
:
_BTextViewSupportBuffer_<_width_table_>(1, 0),
fLock("width buffer")
{
}
WidthBuffer::~WidthBuffer()
{
for (int32 x = 0; x < fItemCount; x++)
delete[] (hashed_escapement*)fBuffer[x].widths;
}
float
WidthBuffer::StringWidth(const char* inText, int32 fromOffset, int32 length,
const BFont* inStyle)
{
if (inText == NULL || length <= 0)
return 0;
BAutolock _(fLock);
int32 index = 0;
if (!FindTable(inStyle, &index))
index = InsertTable(inStyle);
char* text = NULL;
int32 numChars = 0;
int32 textLen = 0;
const char* sourceText = inText + fromOffset;
const float fontSize = inStyle->Size();
float stringWidth = 0;
for (int32 charLen = 0; length > 0;
sourceText += charLen, length -= charLen) {
charLen = UTF8NextCharLen(sourceText, length);
if (charLen <= 0)
break;
const uint32 value = CharToCode(sourceText, charLen);
float escapement;
if (GetEscapement(value, index, &escapement)) {
stringWidth += escapement;
} else {
int32 offset = textLen;
textLen += charLen;
numChars++;
char* newText = (char*)realloc(text, textLen + 1);
if (newText == NULL) {
free(text);
return 0;
}
text = newText;
memcpy(&text[offset], sourceText, charLen);
}
}
if (text != NULL) {
text[textLen] = 0;
stringWidth += HashEscapements(text, numChars, textLen, index, inStyle);
free(text);
}
return stringWidth * fontSize;
}
float
WidthBuffer::StringWidth(TextGapBuffer &inBuffer, int32 fromOffset,
int32 length, const BFont* inStyle)
{
const char* text = inBuffer.GetString(fromOffset, &length);
return StringWidth(text, 0, length, inStyle);
}
bool
WidthBuffer::FindTable(const BFont* inStyle, int32* outIndex)
{
if (inStyle == NULL)
return false;
int32 tableIndex = -1;
for (int32 i = 0; i < fItemCount; i++) {
if (*inStyle == fBuffer[i].font) {
tableIndex = i;
break;
}
}
if (outIndex != NULL)
*outIndex = tableIndex;
return tableIndex != -1;
}
int32
WidthBuffer::InsertTable(const BFont* font)
{
_width_table_ table;
table.font = *font;
table.hashCount = 0;
table.tableCount = kTableCount;
table.widths = new hashed_escapement[kTableCount];
uint32 position = fItemCount;
InsertItemsAt(1, position, &table);
return position;
}
bool
WidthBuffer::GetEscapement(uint32 value, int32 index, float* escapement)
{
const _width_table_ &table = fBuffer[index];
const hashed_escapement* widths
= static_cast<hashed_escapement*>(table.widths);
uint32 hashed = Hash(value) & (table.tableCount - 1);
uint32 found;
while ((found = widths[hashed].code) != kInvalidCode) {
if (found == value)
break;
if (++hashed >= (uint32)table.tableCount)
hashed = 0;
}
if (found == kInvalidCode)
return false;
if (escapement != NULL)
*escapement = widths[hashed].escapement;
return true;
}
uint32
WidthBuffer::Hash(uint32 val)
{
uint32 shifted = val >> 24;
uint32 result = (val >> 15) + (shifted * 3);
result ^= (val >> 6) - (shifted * 22);
result ^= (val << 3);
return result;
}
float
WidthBuffer::HashEscapements(const char* inText, int32 numChars, int32 textLen,
int32 tableIndex, const BFont* inStyle)
{
ASSERT(inText != NULL);
ASSERT(numChars > 0);
ASSERT(textLen > 0);
float* escapements = new float[numChars];
inStyle->GetEscapements(inText, numChars, escapements);
_width_table_ &table = fBuffer[tableIndex];
hashed_escapement* widths = static_cast<hashed_escapement*>(table.widths);
int32 charCount = 0;
char* text = (char*)inText;
const char* textEnd = inText + textLen;
do {
const int32 charLen = UTF8NextCharLen(text);
if (charLen == 0)
break;
const uint32 value = CharToCode(text, charLen);
uint32 hashed = Hash(value) & (table.tableCount - 1);
uint32 found;
while ((found = widths[hashed].code) != kInvalidCode) {
if (found == value)
break;
if (++hashed >= (uint32)table.tableCount)
hashed = 0;
}
if (found == kInvalidCode) {
widths[hashed].code = value;
widths[hashed].escapement = escapements[charCount];
table.hashCount++;
if (table.tableCount * 2 / 3 <= table.hashCount) {
const int32 newSize = table.tableCount * 2;
hashed_escapement* newWidths = new hashed_escapement[newSize];
for (uint32 oldPos = 0; oldPos < (uint32)table.tableCount;
oldPos++) {
if (widths[oldPos].code != kInvalidCode) {
uint32 newPos
= Hash(widths[oldPos].code) & (newSize - 1);
while (newWidths[newPos].code != kInvalidCode) {
if (++newPos >= (uint32)newSize)
newPos = 0;
}
newWidths[newPos] = widths[oldPos];
}
}
delete[] widths;
table.tableCount = newSize;
table.widths = widths = newWidths;
}
}
charCount++;
text += charLen;
} while (text < textEnd);
float width = 0;
for (int32 x = 0; x < numChars; x++)
width += escapements[x];
delete[] escapements;
return width;
}
}
#if __GNUC__ < 3
_BWidthBuffer_::_BWidthBuffer_()
{
}
_BWidthBuffer_::~_BWidthBuffer_()
{
}
_BWidthBuffer_* gCompatibilityWidthBuffer = NULL;
extern "C"
float
StringWidth__14_BWidthBuffer_PCcllPC5BFont(_BWidthBuffer_* widthBuffer,
const char* inText, int32 fromOffset, int32 length, const BFont* inStyle)
{
return BPrivate::gWidthBuffer->StringWidth(inText, fromOffset, length,
inStyle);
}
#endif