/* * Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "StringTruncator.h" #include "CharacterNames.h" #include "Font.h" #include "FontPlatformData.h" #include "TextBreakIterator.h" #include "TextStyle.h" #include #include namespace WebCore { #define STRING_BUFFER_SIZE 2048 typedef unsigned TruncationFunction(const String&, unsigned length, unsigned keepCount, UChar* buffer); 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) { 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); unsigned truncatedLength = omitStart + 1 + (length - omitEnd); ASSERT(truncatedLength <= length); memcpy(buffer, string.characters(), sizeof(UChar) * omitStart); buffer[omitStart] = horizontalEllipsis; memcpy(&buffer[omitStart + 1], &string.characters()[omitEnd], sizeof(UChar) * (length - omitEnd)); return truncatedLength; } static unsigned rightTruncateToBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer) { ASSERT(keepCount < length); ASSERT(keepCount < STRING_BUFFER_SIZE); TextBreakIterator* it = characterBreakIterator(string.characters(), length); unsigned keepLength = textBreakAtOrPreceding(it, keepCount); unsigned truncatedLength = keepLength + 1; memcpy(buffer, string.characters(), sizeof(UChar) * keepLength); buffer[keepLength] = horizontalEllipsis; return truncatedLength; } static float stringWidth(const Font& renderer, const UChar* characters, unsigned length, bool disableRoundingHacks) { TextRun run(characters, length); TextStyle style; if (disableRoundingHacks) style.disableRoundingHacks(); return renderer.floatWidth(run, style); } static String truncateString(const String& string, float maxWidth, const Font& font, TruncationFunction truncateToBuffer, bool disableRoundingHacks) { if (string.isEmpty()) return string; ASSERT(maxWidth >= 0); float currentEllipsisWidth = stringWidth(font, &horizontalEllipsis, 1, disableRoundingHacks); UChar stringBuffer[STRING_BUFFER_SIZE]; unsigned truncatedLength; unsigned keepCount; unsigned length = string.length(); if (length > STRING_BUFFER_SIZE) { keepCount = STRING_BUFFER_SIZE - 1; // need 1 character for the ellipsis truncatedLength = centerTruncateToBuffer(string, length, keepCount, stringBuffer); } else { keepCount = length; memcpy(stringBuffer, string.characters(), sizeof(UChar) * length); truncatedLength = length; } float width = stringWidth(font, stringBuffer, truncatedLength, disableRoundingHacks); if (width <= maxWidth) 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(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); width = stringWidth(font, stringBuffer, truncatedLength, disableRoundingHacks); if (width <= maxWidth) { keepCountForLargestKnownToFit = keepCount; widthForLargestKnownToFit = width; } else { keepCountForSmallestKnownToNotFit = keepCount; widthForSmallestKnownToNotFit = width; } } if (keepCountForLargestKnownToFit == 0) { keepCountForLargestKnownToFit = 1; } if (keepCount != keepCountForLargestKnownToFit) { keepCount = keepCountForLargestKnownToFit; truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer); } return String(stringBuffer, truncatedLength); } String StringTruncator::centerTruncate(const String& string, float maxWidth, const Font& font, bool disableRoundingHacks) { return truncateString(string, maxWidth, font, centerTruncateToBuffer, disableRoundingHacks); } String StringTruncator::rightTruncate(const String& string, float maxWidth, const Font& font, bool disableRoundingHacks) { return truncateString(string, maxWidth, font, rightTruncateToBuffer, disableRoundingHacks); } float StringTruncator::width(const String& string, const Font& font, bool disableRoundingHacks) { return stringWidth(font, string.characters(), string.length(), disableRoundingHacks); } } // namespace WebCore