Home | History | Annotate | Download | only in graphics
      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 "StringTruncator.h"
     31 
     32 #include "Font.h"
     33 #include "TextBreakIterator.h"
     34 #include "TextRun.h"
     35 #include <wtf/Assertions.h>
     36 #include <wtf/Vector.h>
     37 #include <wtf/unicode/CharacterNames.h>
     38 
     39 namespace WebCore {
     40 
     41 #define STRING_BUFFER_SIZE 2048
     42 
     43 typedef unsigned TruncationFunction(const String&, unsigned length, unsigned keepCount, UChar* buffer);
     44 
     45 static inline int textBreakAtOrPreceding(TextBreakIterator* it, int offset)
     46 {
     47     if (isTextBreak(it, offset))
     48         return offset;
     49 
     50     int result = textBreakPreceding(it, offset);
     51     return result == TextBreakDone ? 0 : result;
     52 }
     53 
     54 static inline int boundedTextBreakFollowing(TextBreakIterator* it, int offset, int length)
     55 {
     56     int result = textBreakFollowing(it, offset);
     57     return result == TextBreakDone ? length : result;
     58 }
     59 
     60 static unsigned centerTruncateToBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer)
     61 {
     62     ASSERT(keepCount < length);
     63     ASSERT(keepCount < STRING_BUFFER_SIZE);
     64 
     65     unsigned omitStart = (keepCount + 1) / 2;
     66     TextBreakIterator* it = characterBreakIterator(string.characters(), length);
     67     unsigned omitEnd = boundedTextBreakFollowing(it, omitStart + (length - keepCount) - 1, length);
     68     omitStart = textBreakAtOrPreceding(it, omitStart);
     69 
     70     unsigned truncatedLength = omitStart + 1 + (length - omitEnd);
     71     ASSERT(truncatedLength <= length);
     72 
     73     memcpy(buffer, string.characters(), sizeof(UChar) * omitStart);
     74     buffer[omitStart] = horizontalEllipsis;
     75     memcpy(&buffer[omitStart + 1], &string.characters()[omitEnd], sizeof(UChar) * (length - omitEnd));
     76 
     77     return truncatedLength;
     78 }
     79 
     80 static unsigned rightTruncateToBuffer(const String& string, unsigned length, unsigned keepCount, UChar* buffer)
     81 {
     82     ASSERT(keepCount < length);
     83     ASSERT(keepCount < STRING_BUFFER_SIZE);
     84 
     85     TextBreakIterator* it = characterBreakIterator(string.characters(), length);
     86     unsigned keepLength = textBreakAtOrPreceding(it, keepCount);
     87     unsigned truncatedLength = keepLength + 1;
     88 
     89     memcpy(buffer, string.characters(), sizeof(UChar) * keepLength);
     90     buffer[keepLength] = horizontalEllipsis;
     91 
     92     return truncatedLength;
     93 }
     94 
     95 static float stringWidth(const Font& renderer, const UChar* characters, unsigned length)
     96 {
     97     TextRun run(characters, length);
     98     return renderer.width(run);
     99 }
    100 
    101 static String truncateString(const String& string, float maxWidth, const Font& font, TruncationFunction truncateToBuffer)
    102 {
    103     if (string.isEmpty())
    104         return string;
    105 
    106     ASSERT(maxWidth >= 0);
    107 
    108     float currentEllipsisWidth = stringWidth(font, &horizontalEllipsis, 1);
    109 
    110     UChar stringBuffer[STRING_BUFFER_SIZE];
    111     unsigned truncatedLength;
    112     unsigned keepCount;
    113     unsigned length = string.length();
    114 
    115     if (length > STRING_BUFFER_SIZE) {
    116         keepCount = STRING_BUFFER_SIZE - 1; // need 1 character for the ellipsis
    117         truncatedLength = centerTruncateToBuffer(string, length, keepCount, stringBuffer);
    118     } else {
    119         keepCount = length;
    120         memcpy(stringBuffer, string.characters(), sizeof(UChar) * length);
    121         truncatedLength = length;
    122     }
    123 
    124     float width = stringWidth(font, stringBuffer, truncatedLength);
    125     if (width <= maxWidth)
    126         return string;
    127 
    128     unsigned keepCountForLargestKnownToFit = 0;
    129     float widthForLargestKnownToFit = currentEllipsisWidth;
    130 
    131     unsigned keepCountForSmallestKnownToNotFit = keepCount;
    132     float widthForSmallestKnownToNotFit = width;
    133 
    134     if (currentEllipsisWidth >= maxWidth) {
    135         keepCountForLargestKnownToFit = 1;
    136         keepCountForSmallestKnownToNotFit = 2;
    137     }
    138 
    139     while (keepCountForLargestKnownToFit + 1 < keepCountForSmallestKnownToNotFit) {
    140         ASSERT(widthForLargestKnownToFit <= maxWidth);
    141         ASSERT(widthForSmallestKnownToNotFit > maxWidth);
    142 
    143         float ratio = (keepCountForSmallestKnownToNotFit - keepCountForLargestKnownToFit)
    144             / (widthForSmallestKnownToNotFit - widthForLargestKnownToFit);
    145         keepCount = static_cast<unsigned>(maxWidth * ratio);
    146 
    147         if (keepCount <= keepCountForLargestKnownToFit) {
    148             keepCount = keepCountForLargestKnownToFit + 1;
    149         } else if (keepCount >= keepCountForSmallestKnownToNotFit) {
    150             keepCount = keepCountForSmallestKnownToNotFit - 1;
    151         }
    152 
    153         ASSERT(keepCount < length);
    154         ASSERT(keepCount > 0);
    155         ASSERT(keepCount < keepCountForSmallestKnownToNotFit);
    156         ASSERT(keepCount > keepCountForLargestKnownToFit);
    157 
    158         truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer);
    159 
    160         width = stringWidth(font, stringBuffer, truncatedLength);
    161         if (width <= maxWidth) {
    162             keepCountForLargestKnownToFit = keepCount;
    163             widthForLargestKnownToFit = width;
    164         } else {
    165             keepCountForSmallestKnownToNotFit = keepCount;
    166             widthForSmallestKnownToNotFit = width;
    167         }
    168     }
    169 
    170     if (keepCountForLargestKnownToFit == 0) {
    171         keepCountForLargestKnownToFit = 1;
    172     }
    173 
    174     if (keepCount != keepCountForLargestKnownToFit) {
    175         keepCount = keepCountForLargestKnownToFit;
    176         truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer);
    177     }
    178 
    179     return String(stringBuffer, truncatedLength);
    180 }
    181 
    182 String StringTruncator::centerTruncate(const String& string, float maxWidth, const Font& font)
    183 {
    184     return truncateString(string, maxWidth, font, centerTruncateToBuffer);
    185 }
    186 
    187 String StringTruncator::rightTruncate(const String& string, float maxWidth, const Font& font)
    188 {
    189     return truncateString(string, maxWidth, font, rightTruncateToBuffer);
    190 }
    191 
    192 float StringTruncator::width(const String& string, const Font& font)
    193 {
    194     return stringWidth(font, string.characters(), string.length());
    195 }
    196 
    197 } // namespace WebCore
    198