StringTruncator.cpp [plain text]
#include "config.h"
#include "StringTruncator.h"
#include "CharacterNames.h"
#include "Font.h"
#include "TextBreakIterator.h"
#include <wtf/Assertions.h>
#include <wtf/Vector.h>
namespace WebCore {
#define STRING_BUFFER_SIZE 2048
#define ELLIPSIS_CHARACTER 0x2026
#define SPACE_CHARACTER 0x0020
typedef unsigned TruncationFunction(const String&, unsigned length, unsigned keepCount, UChar* buffer, bool insertEllipsis);
static inline int textBreakAtOrPreceding(TextBreakIterator* it, int offset)
{
if (isTextBreak(it, offset))
return offset;
int result = textBreakPreceding(it, offset);
return result == TextBreakDone ? 0 : result;
}
static inline int boundedTextBreakFollowing(TextBreakIterator* it, int offset, int length)
{
int result = textBreakFollowing(it, offset);
return result == TextBreakDone ? length : result;
}
static unsigned centerTruncateToBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer, bool insertEllipsis)
{
ASSERT(keepCount < length);
ASSERT(keepCount < STRING_BUFFER_SIZE);
unsigned omitStart = (keepCount + 1) / 2;
TextBreakIterator* it = characterBreakIterator(string.characters(), length);
unsigned omitEnd = boundedTextBreakFollowing(it, omitStart + (length - keepCount) - 1, length);
omitStart = textBreakAtOrPreceding(it, omitStart);
if (omitStart > 1 && string[omitStart - 1] != SPACE_CHARACTER &&
omitStart > 2 && string[omitStart - 2] == SPACE_CHARACTER)
omitStart--;
while (omitStart > 1 && string[omitStart - 1] == SPACE_CHARACTER)
omitStart--;
if ((length - omitEnd) > 1 && string[omitEnd] != SPACE_CHARACTER &&
(length - omitEnd) > 2 && string[omitEnd + 1] == SPACE_CHARACTER)
omitEnd++;
while ((length - omitEnd) > 1 && string[omitEnd] == SPACE_CHARACTER)
omitEnd++;
unsigned truncatedLength = insertEllipsis ? omitStart + 1 + (length - omitEnd) : length - (omitEnd - omitStart);
ASSERT(truncatedLength <= length);
memcpy(buffer, string.characters(), sizeof(UChar) * omitStart);
if (insertEllipsis) {
buffer[omitStart] = horizontalEllipsis;
memcpy(&buffer[omitStart + 1], &string.characters()[omitEnd], sizeof(UChar) * (length - omitEnd));
} else {
memcpy(&buffer[omitStart], &string.characters()[omitEnd], sizeof(UChar) * (length - omitEnd));
}
return truncatedLength;
}
static unsigned rightTruncateToBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer, bool insertEllipsis)
{
ASSERT(keepCount < length);
ASSERT(keepCount < STRING_BUFFER_SIZE);
if (keepCount > 1 && string[keepCount - 1] != SPACE_CHARACTER &&
keepCount > 2 && string[keepCount - 2] == SPACE_CHARACTER)
keepCount--;
while (keepCount > 1 && string[keepCount - 1] == SPACE_CHARACTER)
keepCount--;
TextBreakIterator* it = characterBreakIterator(string.characters(), length);
unsigned keepLength = textBreakAtOrPreceding(it, keepCount);
unsigned truncatedLength = insertEllipsis ? keepLength + 1 : keepLength;
memcpy(buffer, string.characters(), sizeof(UChar) * keepLength);
if (insertEllipsis)
buffer[keepLength] = horizontalEllipsis;
return truncatedLength;
}
static unsigned rightClipToCharacterBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer, bool insertEllipsis)
{
UNUSED_PARAM(insertEllipsis);
ASSERT(keepCount < length);
ASSERT(keepCount < STRING_BUFFER_SIZE);
TextBreakIterator* it = characterBreakIterator(string.characters(), length);
unsigned keepLength = textBreakAtOrPreceding(it, keepCount);
memcpy(buffer, string.characters(), sizeof(UChar) * keepLength);
return keepLength;
}
static unsigned rightClipToWordBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer, bool insertEllipsis)
{
UNUSED_PARAM(insertEllipsis);
ASSERT(keepCount < length);
ASSERT(keepCount < STRING_BUFFER_SIZE);
TextBreakIterator* it = wordBreakIterator(string.characters(), length);
unsigned keepLength = textBreakAtOrPreceding(it, keepCount);
memcpy(buffer, string.characters(), sizeof(UChar) * keepLength);
while ((keepLength > 0) && (string[keepLength - 1] == SPACE_CHARACTER))
keepLength--;
return keepLength;
}
static unsigned leftTruncateToBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer, bool insertEllipsis)
{
ASSERT(keepCount < length);
ASSERT(keepCount < STRING_BUFFER_SIZE);
unsigned startIndex = length - keepCount;
TextBreakIterator* it = characterBreakIterator(string.characters(), length);
unsigned adjustedStartIndex = startIndex;
startIndex = boundedTextBreakFollowing(it, startIndex, length - startIndex);
if (adjustedStartIndex < length && string[adjustedStartIndex] != SPACE_CHARACTER &&
adjustedStartIndex < length - 1 && string[adjustedStartIndex + 1] == SPACE_CHARACTER)
adjustedStartIndex++;
while (adjustedStartIndex < length && string[adjustedStartIndex] == SPACE_CHARACTER)
adjustedStartIndex++;
if (insertEllipsis) {
buffer[0] = horizontalEllipsis;
memcpy(&buffer[1], &string.characters()[adjustedStartIndex], sizeof(UChar) * (length - adjustedStartIndex + 1));
return length - adjustedStartIndex + 1;
} else {
memcpy(&buffer[0], &string.characters()[adjustedStartIndex], sizeof(UChar) * (length - adjustedStartIndex + 1));
return length - adjustedStartIndex;
}
}
static float stringWidth(const Font& renderer, const UChar* characters, unsigned length, bool disableRoundingHacks)
{
TextRun run(characters, length);
if (disableRoundingHacks)
run.disableRoundingHacks();
return renderer.floatWidth(run);
}
static String truncateString(const String& string, float maxWidth, const Font& font, TruncationFunction truncateToBuffer, bool disableRoundingHacks, float *resultWidth, bool insertEllipsis = true, float customTruncationElementWidth = 0, bool alwaysTruncate = false)
{
if (string.isEmpty())
return string;
if (resultWidth)
*resultWidth = 0;
ASSERT(maxWidth >= 0);
float currentEllipsisWidth = insertEllipsis ? stringWidth(font, &horizontalEllipsis, 1, disableRoundingHacks) : customTruncationElementWidth;
UChar stringBuffer[STRING_BUFFER_SIZE];
unsigned truncatedLength;
unsigned keepCount;
unsigned length = string.length();
if (length > STRING_BUFFER_SIZE) {
if (insertEllipsis)
keepCount = STRING_BUFFER_SIZE - 1; truncatedLength = centerTruncateToBuffer(string, length, keepCount, stringBuffer, insertEllipsis);
} else {
keepCount = length;
memcpy(stringBuffer, string.characters(), sizeof(UChar) * length);
truncatedLength = length;
}
float width = stringWidth(font, stringBuffer, truncatedLength, disableRoundingHacks);
if (!insertEllipsis && alwaysTruncate)
width += customTruncationElementWidth;
if (width <= maxWidth) {
if (resultWidth)
*resultWidth = width;
return string;
}
unsigned keepCountForLargestKnownToFit = 0;
float widthForLargestKnownToFit = currentEllipsisWidth;
unsigned keepCountForSmallestKnownToNotFit = keepCount;
float widthForSmallestKnownToNotFit = width;
if (currentEllipsisWidth >= maxWidth) {
keepCountForLargestKnownToFit = 1;
keepCountForSmallestKnownToNotFit = 2;
}
while (keepCountForLargestKnownToFit + 1 < keepCountForSmallestKnownToNotFit) {
ASSERT(widthForLargestKnownToFit <= maxWidth);
ASSERT(widthForSmallestKnownToNotFit > maxWidth);
float ratio = (keepCountForSmallestKnownToNotFit - keepCountForLargestKnownToFit)
/ (widthForSmallestKnownToNotFit - widthForLargestKnownToFit);
keepCount = static_cast<unsigned>(maxWidth * ratio);
if (keepCount <= keepCountForLargestKnownToFit) {
keepCount = keepCountForLargestKnownToFit + 1;
} else if (keepCount >= keepCountForSmallestKnownToNotFit) {
keepCount = keepCountForSmallestKnownToNotFit - 1;
}
ASSERT(keepCount < length);
ASSERT(keepCount > 0);
ASSERT(keepCount < keepCountForSmallestKnownToNotFit);
ASSERT(keepCount > keepCountForLargestKnownToFit);
truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer, insertEllipsis);
width = stringWidth(font, stringBuffer, truncatedLength, disableRoundingHacks);
if (!insertEllipsis)
width += customTruncationElementWidth;
if (width <= maxWidth) {
keepCountForLargestKnownToFit = keepCount;
widthForLargestKnownToFit = width;
if (resultWidth)
*resultWidth = width;
} else {
keepCountForSmallestKnownToNotFit = keepCount;
widthForSmallestKnownToNotFit = width;
}
}
if (keepCountForLargestKnownToFit == 0) {
keepCountForLargestKnownToFit = 1;
}
if (keepCount != keepCountForLargestKnownToFit) {
keepCount = keepCountForLargestKnownToFit;
truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer, insertEllipsis);
}
return String(stringBuffer, truncatedLength);
}
String StringTruncator::centerTruncate(const String& string, float maxWidth, const Font& font, bool disableRoundingHacks)
{
return truncateString(string, maxWidth, font, centerTruncateToBuffer, disableRoundingHacks, 0);
}
String StringTruncator::rightTruncate(const String& string, float maxWidth, const Font& font, bool disableRoundingHacks)
{
return truncateString(string, maxWidth, font, rightTruncateToBuffer, disableRoundingHacks, 0);
}
float StringTruncator::width(const String& string, const Font& font, bool disableRoundingHacks)
{
return stringWidth(font, string.characters(), string.length(), disableRoundingHacks);
}
String StringTruncator::centerTruncate(const String& string, float maxWidth, const Font& font, bool disableRoundingHacks, float& resultWidth, bool insertEllipsis, float customTruncationElementWidth)
{
return truncateString(string, maxWidth, font, centerTruncateToBuffer, disableRoundingHacks, &resultWidth, insertEllipsis, customTruncationElementWidth);
}
String StringTruncator::rightTruncate(const String& string, float maxWidth, const Font& font, bool disableRoundingHacks, float& resultWidth, bool insertEllipsis, float customTruncationElementWidth)
{
return truncateString(string, maxWidth, font, rightTruncateToBuffer, disableRoundingHacks, &resultWidth, insertEllipsis, customTruncationElementWidth);
}
String StringTruncator::leftTruncate(const String& string, float maxWidth, const Font& font, bool disableRoundingHacks, float& resultWidth, bool insertEllipsis, float customTruncationElementWidth)
{
return truncateString(string, maxWidth, font, leftTruncateToBuffer, disableRoundingHacks, &resultWidth, insertEllipsis, customTruncationElementWidth);
}
String StringTruncator::rightClipToCharacter(const String& string, float maxWidth, const Font& font, bool disableRoundingHacks, float& resultWidth, bool insertEllipsis, float customTruncationElementWidth)
{
return truncateString(string, maxWidth, font, rightClipToCharacterBuffer, disableRoundingHacks, &resultWidth, insertEllipsis, customTruncationElementWidth);
}
String StringTruncator::rightClipToWord(const String& string, float maxWidth, const Font& font, bool disableRoundingHacks, float& resultWidth, bool insertEllipsis, float customTruncationElementWidth, bool alwaysTruncate)
{
return truncateString(string, maxWidth, font, rightClipToWordBuffer, disableRoundingHacks, &resultWidth, insertEllipsis, customTruncationElementWidth, alwaysTruncate);
}
}