Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright (C) 2005, 2006, 2007 Apple Inc.  All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *
      8  * 1.  Redistributions of source code must retain the above copyright
      9  *     notice, this list of conditions and the following disclaimer.
     10  * 2.  Redistributions in binary form must reproduce the above copyright
     11  *     notice, this list of conditions and the following disclaimer in the
     12  *     documentation and/or other materials provided with the distribution.
     13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     14  *     its contributors may be used to endorse or promote products derived
     15  *     from this software without specific prior written permission.
     16  *
     17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 #include "config.h"
     30 #include "platform/text/StringTruncator.h"
     31 
     32 #include "platform/fonts/Font.h"
     33 #include "platform/text/TextBreakIterator.h"
     34 #include "platform/text/TextRun.h"
     35 #include "wtf/Assertions.h"
     36 #include "wtf/unicode/CharacterNames.h"
     37 
     38 namespace blink {
     39 
     40 #define STRING_BUFFER_SIZE 2048
     41 
     42 typedef unsigned TruncationFunction(const String&, unsigned length, unsigned keepCount, UChar* buffer);
     43 
     44 static inline int textBreakAtOrPreceding(const NonSharedCharacterBreakIterator& it, int offset)
     45 {
     46     if (it.isBreak(offset))
     47         return offset;
     48 
     49     int result = it.preceding(offset);
     50     return result == TextBreakDone ? 0 : result;
     51 }
     52 
     53 static inline int boundedTextBreakFollowing(const NonSharedCharacterBreakIterator& it, int offset, int length)
     54 {
     55     int result = it.following(offset);
     56     return result == TextBreakDone ? length : result;
     57 }
     58 
     59 static unsigned centerTruncateToBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer)
     60 {
     61     ASSERT(keepCount < length);
     62     ASSERT(keepCount < STRING_BUFFER_SIZE);
     63 
     64     unsigned omitStart = (keepCount + 1) / 2;
     65     NonSharedCharacterBreakIterator it(string);
     66     unsigned omitEnd = boundedTextBreakFollowing(it, omitStart + (length - keepCount) - 1, length);
     67     omitStart = textBreakAtOrPreceding(it, omitStart);
     68 
     69     unsigned truncatedLength = omitStart + 1 + (length - omitEnd);
     70     ASSERT(truncatedLength <= length);
     71 
     72     string.copyTo(buffer, 0, omitStart);
     73     buffer[omitStart] = horizontalEllipsis;
     74     string.copyTo(&buffer[omitStart + 1], omitEnd, length - omitEnd);
     75 
     76     return truncatedLength;
     77 }
     78 
     79 static unsigned rightTruncateToBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer)
     80 {
     81     ASSERT(keepCount < length);
     82     ASSERT(keepCount < STRING_BUFFER_SIZE);
     83 
     84     NonSharedCharacterBreakIterator it(string);
     85     unsigned keepLength = textBreakAtOrPreceding(it, keepCount);
     86     unsigned truncatedLength = keepLength + 1;
     87 
     88     string.copyTo(buffer, 0, keepLength);
     89     buffer[keepLength] = horizontalEllipsis;
     90 
     91     return truncatedLength;
     92 }
     93 
     94 static float stringWidth(const Font& renderer, const String& string)
     95 {
     96     TextRun run(string);
     97     return renderer.width(run);
     98 }
     99 
    100 static float stringWidth(const Font& renderer, const UChar* characters, unsigned length)
    101 {
    102     TextRun run(characters, length);
    103     return renderer.width(run);
    104 }
    105 
    106 static String truncateString(const String& string, float maxWidth, const Font& font, TruncationFunction truncateToBuffer)
    107 {
    108     if (string.isEmpty())
    109         return string;
    110 
    111     ASSERT(maxWidth >= 0);
    112 
    113     float currentEllipsisWidth = stringWidth(font, &horizontalEllipsis, 1);
    114 
    115     UChar stringBuffer[STRING_BUFFER_SIZE];
    116     unsigned truncatedLength;
    117     unsigned keepCount;
    118     unsigned length = string.length();
    119 
    120     if (length > STRING_BUFFER_SIZE) {
    121         keepCount = STRING_BUFFER_SIZE - 1; // need 1 character for the ellipsis
    122         truncatedLength = centerTruncateToBuffer(string, length, keepCount, stringBuffer);
    123     } else {
    124         keepCount = length;
    125         string.copyTo(stringBuffer, 0, length);
    126         truncatedLength = length;
    127     }
    128 
    129     float width = stringWidth(font, stringBuffer, truncatedLength);
    130     if (width <= maxWidth)
    131         return string;
    132 
    133     unsigned keepCountForLargestKnownToFit = 0;
    134     float widthForLargestKnownToFit = currentEllipsisWidth;
    135 
    136     unsigned keepCountForSmallestKnownToNotFit = keepCount;
    137     float widthForSmallestKnownToNotFit = width;
    138 
    139     if (currentEllipsisWidth >= maxWidth) {
    140         keepCountForLargestKnownToFit = 1;
    141         keepCountForSmallestKnownToNotFit = 2;
    142     }
    143 
    144     while (keepCountForLargestKnownToFit + 1 < keepCountForSmallestKnownToNotFit) {
    145         ASSERT(widthForLargestKnownToFit <= maxWidth);
    146         ASSERT(widthForSmallestKnownToNotFit > maxWidth);
    147 
    148         float ratio = (keepCountForSmallestKnownToNotFit - keepCountForLargestKnownToFit)
    149             / (widthForSmallestKnownToNotFit - widthForLargestKnownToFit);
    150         keepCount = static_cast<unsigned>(maxWidth * ratio);
    151 
    152         if (keepCount <= keepCountForLargestKnownToFit) {
    153             keepCount = keepCountForLargestKnownToFit + 1;
    154         } else if (keepCount >= keepCountForSmallestKnownToNotFit) {
    155             keepCount = keepCountForSmallestKnownToNotFit - 1;
    156         }
    157 
    158         ASSERT(keepCount < length);
    159         ASSERT(keepCount > 0);
    160         ASSERT(keepCount < keepCountForSmallestKnownToNotFit);
    161         ASSERT(keepCount > keepCountForLargestKnownToFit);
    162 
    163         truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer);
    164 
    165         width = stringWidth(font, stringBuffer, truncatedLength);
    166         if (width <= maxWidth) {
    167             keepCountForLargestKnownToFit = keepCount;
    168             widthForLargestKnownToFit = width;
    169         } else {
    170             keepCountForSmallestKnownToNotFit = keepCount;
    171             widthForSmallestKnownToNotFit = width;
    172         }
    173     }
    174 
    175     if (!keepCountForLargestKnownToFit)
    176         keepCountForLargestKnownToFit = 1;
    177 
    178     if (keepCount != keepCountForLargestKnownToFit) {
    179         keepCount = keepCountForLargestKnownToFit;
    180         truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer);
    181     }
    182 
    183     return String(stringBuffer, truncatedLength);
    184 }
    185 
    186 String StringTruncator::centerTruncate(const String& string, float maxWidth, const Font& font)
    187 {
    188     return truncateString(string, maxWidth, font, centerTruncateToBuffer);
    189 }
    190 
    191 String StringTruncator::rightTruncate(const String& string, float maxWidth, const Font& font)
    192 {
    193     return truncateString(string, maxWidth, font, rightTruncateToBuffer);
    194 }
    195 
    196 float StringTruncator::width(const String& string, const Font& font)
    197 {
    198     return stringWidth(font, string);
    199 }
    200 
    201 } // namespace blink
    202