* Replace the truncate_string() helper function with a new, simplified version.
* Remove no longer necessary support functions. * The new version uses a single BString as input/output parameter and only modifies that one by removing non-fitting chars and inserting the ellipsis where appropriate, so avoids copying around bytes/chars/strings in a few places. It uses the new Chars functions of BString so also no need for manual multibyte handling. * Adjusted the BFont and ServerFont usage of truncate_string() which are both simplified by using the single BString. It avoids a lot of temprary allocations and string copying. The char * version of BFont GetTruncatedStrings() now uses the BString version and not the other way around anymore which requires us to allocate temporary BString objects, it's not worse than before though. * This fixes a bunch of problems with the previous functions like always prepending the ellipsis for B_TRUNCATE_BEGINNING, crashing on short enough widths, violating the width in the B_TRUNCATE_END case when the width was short enough, non-optimal truncation in a few cases and sometimes truncation where none would've been needed. Also fixes #4128 which was a symptom of the broken B_TRUNCATE_BEGINNING. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@35381 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
parent
ecbd2b3054
commit
5bc9fb9d24
@ -1,22 +1,14 @@
|
||||
/*
|
||||
* Copyright 2005, Stephan Aßmus <superstippi@gmx.de>. All rights reserved.
|
||||
* Copyright 2010, Michael Lotz <mmlr@mlotz.ch>. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*
|
||||
* a helper function to truncate strings
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef TRUNCATE_STRING_H
|
||||
#define TRUNCATE_STRING_H
|
||||
|
||||
#include <SupportDefs.h>
|
||||
|
||||
// truncated_string
|
||||
void
|
||||
truncate_string(const char* string,
|
||||
uint32 mode, float width, char* result,
|
||||
const float* escapementArray, float fontSize,
|
||||
float ellipsisWidth, int32 length, int32 numChars);
|
||||
void truncate_string(BString& string, uint32 mode, float width,
|
||||
const float* escapementArray, float fontSize, float ellipsisWidth,
|
||||
int32 numChars);
|
||||
|
||||
#endif // STRING_TRUNCATION_H
|
||||
#endif // TRUNCATE_STRING_H
|
||||
|
@ -971,22 +971,23 @@ void
|
||||
BFont::GetTruncatedStrings(const char *stringArray[], int32 numStrings,
|
||||
uint32 mode, float width, BString resultArray[]) const
|
||||
{
|
||||
if (stringArray && resultArray && numStrings > 0) {
|
||||
// allocate storage, see BeBook for "+ 3" (make space for ellipsis)
|
||||
char** truncatedStrings = new char*[numStrings];
|
||||
for (int32 i = 0; i < numStrings; i++) {
|
||||
truncatedStrings[i] = new char[strlen(stringArray[i]) + 3];
|
||||
}
|
||||
if (stringArray != NULL && numStrings > 0) {
|
||||
// the width of the "…" glyph
|
||||
float ellipsisWidth = StringWidth(B_UTF8_ELLIPSIS);
|
||||
|
||||
GetTruncatedStrings(stringArray, numStrings, mode, width,
|
||||
truncatedStrings);
|
||||
|
||||
// copy the strings into the BString array and free each one
|
||||
for (int32 i = 0; i < numStrings; i++) {
|
||||
resultArray[i].SetTo(truncatedStrings[i]);
|
||||
delete[] truncatedStrings[i];
|
||||
resultArray[i] = stringArray[i];
|
||||
int32 numChars = resultArray[i].CountChars();
|
||||
|
||||
// get the escapement of each glyph in font units
|
||||
float *escapementArray = new float[numChars];
|
||||
GetEscapements(stringArray[i], numChars, NULL, escapementArray);
|
||||
|
||||
truncate_string(resultArray[i], mode, width, escapementArray,
|
||||
fSize, ellipsisWidth, numChars);
|
||||
|
||||
delete[] escapementArray;
|
||||
}
|
||||
delete[] truncatedStrings;
|
||||
}
|
||||
}
|
||||
|
||||
@ -995,21 +996,15 @@ void
|
||||
BFont::GetTruncatedStrings(const char *stringArray[], int32 numStrings,
|
||||
uint32 mode, float width, char *resultArray[]) const
|
||||
{
|
||||
if (stringArray && numStrings > 0) {
|
||||
// the width of the "…" glyph
|
||||
float ellipsisWidth = StringWidth(B_UTF8_ELLIPSIS);
|
||||
if (stringArray != NULL && numStrings > 0) {
|
||||
for (int32 i = 0; i < numStrings; i++) {
|
||||
int32 length = strlen(stringArray[i]);
|
||||
// count the individual glyphs
|
||||
int32 numChars = UTF8CountChars(stringArray[i], length);
|
||||
// get the escapement of each glyph in font units
|
||||
float* escapementArray = new float[numChars];
|
||||
GetEscapements(stringArray[i], numChars, NULL, escapementArray);
|
||||
BString *strings = new BString[numStrings];
|
||||
GetTruncatedStrings(stringArray, numStrings, mode, width, strings);
|
||||
|
||||
truncate_string(stringArray[i], mode, width, resultArray[i],
|
||||
escapementArray, fSize, ellipsisWidth, length, numChars);
|
||||
for (int32 i = 0; i < numStrings; i++)
|
||||
strcpy(resultArray[i], strings[i].String());
|
||||
|
||||
delete[] escapementArray;
|
||||
delete[] strings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
/*
|
||||
* Copyright 2001-2009, Haiku, Inc.
|
||||
* Copyright 2001-2010, Haiku, Inc.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*
|
||||
* Authors:
|
||||
* DarkWyrm <bpmagic@columbus.rr.com>
|
||||
* Caz <turok2@currantbun.com>
|
||||
* Axel Dörfler, axeld@pinc-software.de
|
||||
* Michael Lotz <mmlr@mlotz.ch>
|
||||
*/
|
||||
|
||||
|
||||
@ -1361,259 +1362,134 @@ do_minimize_team(BRect zoomRect, team_id team, bool zoom)
|
||||
// #pragma mark - truncate string
|
||||
|
||||
|
||||
static char*
|
||||
write_ellipsis(char* dst)
|
||||
{
|
||||
strcpy(dst, B_UTF8_ELLIPSIS);
|
||||
// The UTF-8 character spans over 3 bytes
|
||||
return dst + 3;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
optional_char_fits(float escapement, float fontSize, float gap)
|
||||
{
|
||||
const float size = escapement * fontSize;
|
||||
if (size <= gap || fabs(size - gap) <= 0.0001)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
truncate_end(const char* source, char* dest, uint32 numChars,
|
||||
const float* escapementArray, float width, float ellipsisWidth, float size)
|
||||
{
|
||||
float currentWidth = 0.0;
|
||||
ellipsisWidth /= size;
|
||||
// test if this is as accurate as escapementArray * size
|
||||
width /= size;
|
||||
uint32 lastFit = 0, c;
|
||||
|
||||
for (c = 0; c < numChars; c++) {
|
||||
currentWidth += escapementArray[c];
|
||||
if (currentWidth + ellipsisWidth <= width)
|
||||
lastFit = c;
|
||||
|
||||
if (currentWidth > width)
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == numChars) {
|
||||
// string fits into width
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c == 0) {
|
||||
// there is no space for the ellipsis
|
||||
strcpy(dest, "");
|
||||
return true;
|
||||
}
|
||||
|
||||
// copy string to destination
|
||||
|
||||
for (uint32 i = 0; i < lastFit + 1; i++) {
|
||||
// copy one glyph
|
||||
do {
|
||||
*dest++ = *source++;
|
||||
} while (IsInsideGlyph(*source));
|
||||
}
|
||||
|
||||
// write ellipsis and terminate
|
||||
|
||||
dest = write_ellipsis(dest);
|
||||
*dest = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static char*
|
||||
copy_from_end(const char* src, char* dst, uint32 numChars, uint32 length,
|
||||
const float* escapementArray, float width, float ellipsisWidth, float size)
|
||||
{
|
||||
const char* originalStart = src;
|
||||
src += length - 1;
|
||||
float currentWidth = 0.0;
|
||||
for (int32 c = numChars - 1; c > 0; c--) {
|
||||
currentWidth += escapementArray[c] * size;
|
||||
if (currentWidth > width) {
|
||||
// ups, we definitely don't fit. go back until the ellipsis fits
|
||||
currentWidth += ellipsisWidth;
|
||||
// go forward again until ellipsis fits (already beyond the target)
|
||||
for (uint32 c2 = c; c2 < numChars; c2++) {
|
||||
//printf(" backward: %c (%ld) (%.1f - %.1f = %.1f)\n", *dst, c2, currentWidth, escapementArray[c2] * size,
|
||||
// currentWidth - escapementArray[c2] * size);
|
||||
currentWidth -= escapementArray[c2] * size;
|
||||
do {
|
||||
src++;
|
||||
} while (IsInsideGlyph(*src));
|
||||
// see if we went back enough
|
||||
if (currentWidth <= width)
|
||||
break;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
// go back one glyph
|
||||
do {
|
||||
src--;
|
||||
} while (IsInsideGlyph(*src));
|
||||
}
|
||||
}
|
||||
// copy from the end of the string
|
||||
uint32 bytesToCopy = originalStart + length - src;
|
||||
memcpy(dst, src, bytesToCopy);
|
||||
dst += bytesToCopy;
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
truncate_middle(const char* source, char* dest, uint32 numChars,
|
||||
const float* escapementArray, float width, float ellipsisWidth, float size)
|
||||
{
|
||||
float mid = (width - ellipsisWidth) / 2.0;
|
||||
|
||||
uint32 left = 0;
|
||||
float leftWidth = 0.0;
|
||||
while (left < numChars && (leftWidth + (escapementArray[left] * size))
|
||||
< mid) {
|
||||
leftWidth += (escapementArray[left++] * size);
|
||||
}
|
||||
|
||||
if (left == numChars)
|
||||
return false;
|
||||
|
||||
float rightWidth = 0.0;
|
||||
uint32 right = numChars;
|
||||
while (right > left && (rightWidth + (escapementArray[right - 1] * size))
|
||||
< mid) {
|
||||
rightWidth += (escapementArray[--right] * size);
|
||||
}
|
||||
|
||||
if (left >= right)
|
||||
return false;
|
||||
|
||||
float stringWidth = leftWidth + rightWidth;
|
||||
for (uint32 i = left; i < right; ++i)
|
||||
stringWidth += (escapementArray[i] * size);
|
||||
|
||||
if (stringWidth <= width)
|
||||
return false;
|
||||
|
||||
// if there is no space for the ellipsis
|
||||
if (width < ellipsisWidth) {
|
||||
strcpy(dest, "");
|
||||
return true;
|
||||
}
|
||||
|
||||
// The ellipsis now definitely fits, but let's
|
||||
// see if we can add another character
|
||||
float gap = width - (leftWidth + ellipsisWidth + rightWidth);
|
||||
if (left > numChars - right) {
|
||||
// try right letter first
|
||||
if (optional_char_fits(escapementArray[right - 1], size, gap))
|
||||
right--;
|
||||
else if (optional_char_fits(escapementArray[left], size, gap))
|
||||
left++;
|
||||
} else {
|
||||
// try left letter first
|
||||
if (optional_char_fits(escapementArray[left], size, gap))
|
||||
left++;
|
||||
else if (optional_char_fits(escapementArray[right - 1], size, gap))
|
||||
right--;
|
||||
}
|
||||
|
||||
// copy characters
|
||||
|
||||
for (uint32 i = 0; i < left; i++) {
|
||||
// copy one glyph
|
||||
do {
|
||||
*dest++ = *source++;
|
||||
} while (IsInsideGlyph(*source));
|
||||
}
|
||||
|
||||
dest = write_ellipsis(dest);
|
||||
|
||||
for (uint32 i = left; i < numChars; i++) {
|
||||
// copy one glyph
|
||||
do {
|
||||
if (i >= right)
|
||||
*dest++ = *source++;
|
||||
else
|
||||
source++;
|
||||
} while (IsInsideGlyph(*source));
|
||||
}
|
||||
|
||||
// terminate
|
||||
dest[0] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// TODO: put into BPrivate namespace
|
||||
void
|
||||
truncate_string(const char* string, uint32 mode, float width,
|
||||
char* result, const float* escapementArray, float fontSize,
|
||||
float ellipsisWidth, int32 length, int32 numChars)
|
||||
truncate_string(BString& string, uint32 mode, float width,
|
||||
const float* escapementArray, float fontSize, float ellipsisWidth,
|
||||
int32 charCount)
|
||||
{
|
||||
// TODO: that's actually not correct: the string could be smaller than
|
||||
// ellipsisWidth
|
||||
if (string == NULL /*|| width < ellipsisWidth*/) {
|
||||
// we don't have room for a single glyph
|
||||
strcpy(result, "");
|
||||
return;
|
||||
}
|
||||
|
||||
// iterate over glyphs and copy source into result string
|
||||
// one glyph at a time as long as we have room for the "…" yet
|
||||
char* dest = result;
|
||||
const char* source = string;
|
||||
bool truncated = true;
|
||||
// add a tiny amount to the width to make floating point inaccuracy
|
||||
// not drop chars that would actually fit exactly
|
||||
width += 0.00001;
|
||||
|
||||
switch (mode) {
|
||||
case B_TRUNCATE_BEGINNING: {
|
||||
dest = copy_from_end(source, dest, numChars, length,
|
||||
escapementArray, width, ellipsisWidth, fontSize);
|
||||
// "dst" points to the position behind the last glyph that
|
||||
// was copied.
|
||||
int32 dist = dest - result;
|
||||
// we didn't terminate yet
|
||||
*dest = 0;
|
||||
if (dist < length) {
|
||||
// TODO: Is there a smarter way?
|
||||
char* temp = new char[dist + 4];
|
||||
char* t = temp;
|
||||
// append "…"
|
||||
t = write_ellipsis(t);
|
||||
// shuffle arround strings so that "…" is prepended
|
||||
strcpy(t, result);
|
||||
strcpy(result, temp);
|
||||
delete[] temp;
|
||||
/* char t = result[3];
|
||||
memmove(&result[3], result, dist + 1);
|
||||
write_ellipsis(result);
|
||||
result[3] = t;*/
|
||||
case B_TRUNCATE_BEGINNING:
|
||||
{
|
||||
float totalWidth = 0;
|
||||
for (int32 i = charCount - 1; i >= 0; i--) {
|
||||
float charWidth = escapementArray[i] * fontSize;
|
||||
if (totalWidth + charWidth > width) {
|
||||
// we need to truncate
|
||||
while (totalWidth + ellipsisWidth > width) {
|
||||
// remove chars until there's enough space for the
|
||||
// ellipsis
|
||||
if (++i == charCount) {
|
||||
// we've reached the end of the string and still
|
||||
// no space, so return an empty string
|
||||
string.Truncate(0);
|
||||
return;
|
||||
}
|
||||
|
||||
totalWidth -= escapementArray[i] * fontSize;
|
||||
}
|
||||
|
||||
string.RemoveChars(0, i + 1);
|
||||
string.PrependChars(B_UTF8_ELLIPSIS, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
totalWidth += charWidth;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case B_TRUNCATE_END:
|
||||
truncated = truncate_end(source, dest, numChars, escapementArray,
|
||||
width, ellipsisWidth, fontSize);
|
||||
break;
|
||||
{
|
||||
float totalWidth = 0;
|
||||
for (int32 i = 0; i < charCount; i++) {
|
||||
float charWidth = escapementArray[i] * fontSize;
|
||||
if (totalWidth + charWidth > width) {
|
||||
// we need to truncate
|
||||
while (totalWidth + ellipsisWidth > width) {
|
||||
// remove chars until there's enough space for the
|
||||
// ellipsis
|
||||
if (i-- == 0) {
|
||||
// we've reached the start of the string and still
|
||||
// no space, so return an empty string
|
||||
string.Truncate(0);
|
||||
return;
|
||||
}
|
||||
|
||||
totalWidth -= escapementArray[i] * fontSize;
|
||||
}
|
||||
|
||||
string.RemoveChars(i, charCount - i);
|
||||
string.AppendChars(B_UTF8_ELLIPSIS, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
totalWidth += charWidth;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case B_TRUNCATE_SMART:
|
||||
// TODO: implement, though it was never implemented on R5
|
||||
// FALL THROUGH (at least do something)
|
||||
case B_TRUNCATE_MIDDLE:
|
||||
default:
|
||||
truncated = truncate_middle(source, dest, numChars,
|
||||
escapementArray, width, ellipsisWidth, fontSize);
|
||||
case B_TRUNCATE_SMART:
|
||||
{
|
||||
float leftWidth = 0;
|
||||
float rightWidth = 0;
|
||||
int32 leftIndex = 0;
|
||||
int32 rightIndex = charCount - 1;
|
||||
bool left = true;
|
||||
|
||||
for (int32 i = 0; i < charCount; i++) {
|
||||
float charWidth
|
||||
= escapementArray[left ? leftIndex : rightIndex] * fontSize;
|
||||
|
||||
if (leftWidth + rightWidth + charWidth > width) {
|
||||
// we need to truncate
|
||||
while (leftWidth + rightWidth + ellipsisWidth > width) {
|
||||
// remove chars until there's enough space for the
|
||||
// ellipsis
|
||||
if (leftIndex == 0 && rightIndex == charCount - 1) {
|
||||
// we've reached both ends of the string and still
|
||||
// no space, so return an empty string
|
||||
string.Truncate(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (leftIndex > 0 && (rightIndex == charCount - 1
|
||||
|| leftWidth > rightWidth)) {
|
||||
// remove char on the left
|
||||
leftWidth -= escapementArray[--leftIndex]
|
||||
* fontSize;
|
||||
} else {
|
||||
// remove char on the right
|
||||
rightWidth -= escapementArray[++rightIndex]
|
||||
* fontSize;
|
||||
}
|
||||
}
|
||||
|
||||
string.RemoveChars(leftIndex, rightIndex + 1 - leftIndex);
|
||||
string.InsertChars(B_UTF8_ELLIPSIS, 1, leftIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (left) {
|
||||
leftIndex++;
|
||||
leftWidth += charWidth;
|
||||
} else {
|
||||
rightIndex--;
|
||||
rightWidth += charWidth;
|
||||
}
|
||||
|
||||
left = rightWidth > leftWidth;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!truncated) {
|
||||
// copy string to destination verbatim
|
||||
strlcpy(dest, source, length + 1);
|
||||
}
|
||||
// we've run through without the need to truncate, leave the string as it is
|
||||
}
|
||||
|
@ -867,28 +867,20 @@ ServerFont::TruncateString(BString* inOut, uint32 mode, float width) const
|
||||
|
||||
// the width of the "…" glyph
|
||||
float ellipsisWidth = StringWidth(B_UTF8_ELLIPSIS, strlen(B_UTF8_ELLIPSIS));
|
||||
const char* string = inOut->String();
|
||||
int32 length = inOut->Length();
|
||||
|
||||
// temporary array to hold result
|
||||
char* result = new char[length + 3];
|
||||
|
||||
// count the individual glyphs
|
||||
int32 numChars = UTF8CountChars(string, -1);
|
||||
int32 numChars = inOut->CountChars();
|
||||
|
||||
// get the escapement of each glyph in font units
|
||||
float* escapementArray = new float[numChars];
|
||||
static escapement_delta delta = (escapement_delta){ 0.0, 0.0 };
|
||||
if (GetEscapements(string, length, numChars, delta, escapementArray)
|
||||
== B_OK) {
|
||||
truncate_string(string, mode, width, result, escapementArray, fSize,
|
||||
ellipsisWidth, length, numChars);
|
||||
|
||||
inOut->SetTo(result);
|
||||
if (GetEscapements(inOut->String(), inOut->Length(), numChars, delta,
|
||||
escapementArray) == B_OK) {
|
||||
truncate_string(*inOut, mode, width, escapementArray, fSize,
|
||||
ellipsisWidth, numChars);
|
||||
}
|
||||
|
||||
delete[] escapementArray;
|
||||
delete[] result;
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user