Author: mmlr Date: 2010-02-01 19:43:03 +0100 (Mon, 01 Feb 2010) New Revision: 35381 Changeset: http://dev.haiku-os.org/changeset/35381/haiku Ticket: http://dev.haiku-os.org/ticket/4128 Modified: haiku/trunk/headers/private/interface/truncate_string.h haiku/trunk/src/kits/interface/Font.cpp haiku/trunk/src/kits/interface/InterfaceDefs.cpp haiku/trunk/src/servers/app/ServerFont.cpp Log: * 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. Modified: haiku/trunk/headers/private/interface/truncate_string.h =================================================================== --- haiku/trunk/headers/private/interface/truncate_string.h 2010-02-01 17:04:49 UTC (rev 35380) +++ haiku/trunk/headers/private/interface/truncate_string.h 2010-02-01 18:43:03 UTC (rev 35381) @@ -1,22 +1,14 @@ /* - * Copyright 2005, Stephan Aßmus <superstippi@xxxxxx>. All rights reserved. + * Copyright 2010, Michael Lotz <mmlr@xxxxxxxx>. 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 Modified: haiku/trunk/src/kits/interface/Font.cpp =================================================================== --- haiku/trunk/src/kits/interface/Font.cpp 2010-02-01 17:04:49 UTC (rev 35380) +++ haiku/trunk/src/kits/interface/Font.cpp 2010-02-01 18:43:03 UTC (rev 35381) @@ -971,22 +971,23 @@ 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]; + if (stringArray != NULL && numStrings > 0) { + // the width of the "…" glyph + float ellipsisWidth = StringWidth(B_UTF8_ELLIPSIS); + for (int32 i = 0; i < numStrings; i++) { - truncatedStrings[i] = new char[strlen(stringArray[i]) + 3]; - } + resultArray[i] = stringArray[i]; + int32 numChars = resultArray[i].CountChars(); - GetTruncatedStrings(stringArray, numStrings, mode, width, - truncatedStrings); + // get the escapement of each glyph in font units + float *escapementArray = new float[numChars]; + GetEscapements(stringArray[i], numChars, NULL, escapementArray); - // 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]; + truncate_string(resultArray[i], mode, width, escapementArray, + fSize, ellipsisWidth, numChars); + + delete[] escapementArray; } - delete[] truncatedStrings; } } @@ -995,21 +996,15 @@ 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; } } } Modified: haiku/trunk/src/kits/interface/InterfaceDefs.cpp =================================================================== --- haiku/trunk/src/kits/interface/InterfaceDefs.cpp 2010-02-01 17:04:49 UTC (rev 35380) +++ haiku/trunk/src/kits/interface/InterfaceDefs.cpp 2010-02-01 18:43:03 UTC (rev 35381) @@ -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@xxxxxxxxxxxxxxx> * Caz <turok2@xxxxxxxxxxxxxx> * Axel Dörfler, axeld@xxxxxxxxxxxxxxxx + * Michael Lotz <mmlr@xxxxxxxx> */ @@ -1361,259 +1362,134 @@ // #pragma mark - truncate string -static char* -write_ellipsis(char* dst) +void +truncate_string(BString& string, uint32 mode, float width, + const float* escapementArray, float fontSize, float ellipsisWidth, + int32 charCount) { - strcpy(dst, B_UTF8_ELLIPSIS); - // The UTF-8 character spans over 3 bytes - return dst + 3; -} + // 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: + { + 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; + } -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; -} + totalWidth -= escapementArray[i] * fontSize; + } + string.RemoveChars(0, i + 1); + string.PrependChars(B_UTF8_ELLIPSIS, 1); + return; + } -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; + totalWidth += charWidth; + } - 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; - } + case B_TRUNCATE_END: + { + 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; + } - if (c == 0) { - // there is no space for the ellipsis - strcpy(dest, ""); - return true; - } + totalWidth -= escapementArray[i] * fontSize; + } - // copy string to destination + string.RemoveChars(i, charCount - i); + string.AppendChars(B_UTF8_ELLIPSIS, 1); + return; + } - 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; + totalWidth += charWidth; } + 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; -} + case B_TRUNCATE_MIDDLE: + case B_TRUNCATE_SMART: + { + float leftWidth = 0; + float rightWidth = 0; + int32 leftIndex = 0; + int32 rightIndex = charCount - 1; + bool left = true; -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; + for (int32 i = 0; i < charCount; i++) { + float charWidth + = escapementArray[left ? leftIndex : rightIndex] * fontSize; - uint32 left = 0; - float leftWidth = 0.0; - while (left < numChars && (leftWidth + (escapementArray[left] * size)) - < mid) { - leftWidth += (escapementArray[left++] * size); - } + 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 (left == numChars) - return false; + 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; + } + } - float rightWidth = 0.0; - uint32 right = numChars; - while (right > left && (rightWidth + (escapementArray[right - 1] * size)) - < mid) { - rightWidth += (escapementArray[--right] * size); - } + string.RemoveChars(leftIndex, rightIndex + 1 - leftIndex); + string.InsertChars(B_UTF8_ELLIPSIS, 1, leftIndex); + return; + } - if (left >= right) - return false; + if (left) { + leftIndex++; + leftWidth += charWidth; + } else { + rightIndex--; + rightWidth += charWidth; + } - 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) -{ - // 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; - - 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;*/ + left = rightWidth > leftWidth; } - break; - } - case B_TRUNCATE_END: - truncated = truncate_end(source, dest, numChars, escapementArray, - width, ellipsisWidth, fontSize); 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); - 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 } Modified: haiku/trunk/src/servers/app/ServerFont.cpp =================================================================== --- haiku/trunk/src/servers/app/ServerFont.cpp 2010-02-01 17:04:49 UTC (rev 35380) +++ haiku/trunk/src/servers/app/ServerFont.cpp 2010-02-01 18:43:03 UTC (rev 35381) @@ -867,28 +867,20 @@ // 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; }