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 "core/platform/graphics/StringTruncator.h"
     31 
     32 #include "core/platform/graphics/Font.h"
     33 #include "core/platform/graphics/TextRun.h"
     34 #include "core/platform/text/TextBreakIterator.h"
     35 #include "wtf/Assertions.h"
     36 #include "wtf/unicode/CharacterNames.h"
     37 #include "wtf/Vector.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(const NonSharedCharacterBreakIterator& it, int offset)
     46 {
     47     if (it.isBreak(offset))
     48         return offset;
     49 
     50     int result = it.preceding(offset);
     51     return result == TextBreakDone ? 0 : result;
     52 }
     53 
     54 static inline int boundedTextBreakFollowing(const NonSharedCharacterBreakIterator& it, int offset, int length)
     55 {
     56     int result = it.following(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     NonSharedCharacterBreakIterator it(string);
     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     string.copyTo(buffer, 0, omitStart);
     74     buffer[omitStart] = horizontalEllipsis;
     75     string.copyTo(&buffer[omitStart + 1], omitEnd, 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     NonSharedCharacterBreakIterator it(string);
     86     unsigned keepLength = textBreakAtOrPreceding(it, keepCount);
     87     unsigned truncatedLength = keepLength + 1;
     88 
     89     string.copyTo(buffer, 0, keepLength);
     90     buffer[keepLength] = horizontalEllipsis;
     91 
     92     return truncatedLength;
     93 }
     94 
     95 static float stringWidth(const Font& renderer, const String& string, bool disableRoundingHacks)
     96 {
     97     TextRun run(string);
     98     if (disableRoundingHacks)
     99         run.disableRoundingHacks();
    100     return renderer.width(run);
    101 }
    102 
    103 static float stringWidth(const Font& renderer, const UChar* characters, unsigned length, bool disableRoundingHacks)
    104 {
    105     TextRun run(characters, length);
    106     if (disableRoundingHacks)
    107         run.disableRoundingHacks();
    108     return renderer.width(run);
    109 }
    110 
    111 static String truncateString(const String& string, float maxWidth, const Font& font, TruncationFunction truncateToBuffer, bool disableRoundingHacks)
    112 {
    113     if (string.isEmpty())
    114         return string;
    115 
    116     ASSERT(maxWidth >= 0);
    117 
    118     float currentEllipsisWidth = stringWidth(font, &horizontalEllipsis, 1, disableRoundingHacks);
    119 
    120     UChar stringBuffer[STRING_BUFFER_SIZE];
    121     unsigned truncatedLength;
    122     unsigned keepCount;
    123     unsigned length = string.length();
    124 
    125     if (length > STRING_BUFFER_SIZE) {
    126         keepCount = STRING_BUFFER_SIZE - 1; // need 1 character for the ellipsis
    127         truncatedLength = centerTruncateToBuffer(string, length, keepCount, stringBuffer);
    128     } else {
    129         keepCount = length;
    130         string.copyTo(stringBuffer, 0, length);
    131         truncatedLength = length;
    132     }
    133 
    134     float width = stringWidth(font, stringBuffer, truncatedLength, disableRoundingHacks);
    135     if (width <= maxWidth)
    136         return string;
    137 
    138     unsigned keepCountForLargestKnownToFit = 0;
    139     float widthForLargestKnownToFit = currentEllipsisWidth;
    140 
    141     unsigned keepCountForSmallestKnownToNotFit = keepCount;
    142     float widthForSmallestKnownToNotFit = width;
    143 
    144     if (currentEllipsisWidth >= maxWidth) {
    145         keepCountForLargestKnownToFit = 1;
    146         keepCountForSmallestKnownToNotFit = 2;
    147     }
    148 
    149     while (keepCountForLargestKnownToFit + 1 < keepCountForSmallestKnownToNotFit) {
    150         ASSERT(widthForLargestKnownToFit <= maxWidth);
    151         ASSERT(widthForSmallestKnownToNotFit > maxWidth);
    152 
    153         float ratio = (keepCountForSmallestKnownToNotFit - keepCountForLargestKnownToFit)
    154             / (widthForSmallestKnownToNotFit - widthForLargestKnownToFit);
    155         keepCount = static_cast<unsigned>(maxWidth * ratio);
    156 
    157         if (keepCount <= keepCountForLargestKnownToFit) {
    158             keepCount = keepCountForLargestKnownToFit + 1;
    159         } else if (keepCount >= keepCountForSmallestKnownToNotFit) {
    160             keepCount = keepCountForSmallestKnownToNotFit - 1;
    161         }
    162 
    163         ASSERT(keepCount < length);
    164         ASSERT(keepCount > 0);
    165         ASSERT(keepCount < keepCountForSmallestKnownToNotFit);
    166         ASSERT(keepCount > keepCountForLargestKnownToFit);
    167 
    168         truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer);
    169 
    170         width = stringWidth(font, stringBuffer, truncatedLength, disableRoundingHacks);
    171         if (width <= maxWidth) {
    172             keepCountForLargestKnownToFit = keepCount;
    173             widthForLargestKnownToFit = width;
    174         } else {
    175             keepCountForSmallestKnownToNotFit = keepCount;
    176             widthForSmallestKnownToNotFit = width;
    177         }
    178     }
    179 
    180     if (keepCountForLargestKnownToFit == 0) {
    181         keepCountForLargestKnownToFit = 1;
    182     }
    183 
    184     if (keepCount != keepCountForLargestKnownToFit) {
    185         keepCount = keepCountForLargestKnownToFit;
    186         truncatedLength = truncateToBuffer(string, length, keepCount, stringBuffer);
    187     }
    188 
    189     return String(stringBuffer, truncatedLength);
    190 }
    191 
    192 String StringTruncator::centerTruncate(const String& string, float maxWidth, const Font& font, EnableRoundingHacksOrNot enableRoundingHacks)
    193 {
    194     return truncateString(string, maxWidth, font, centerTruncateToBuffer, !enableRoundingHacks);
    195 }
    196 
    197 String StringTruncator::rightTruncate(const String& string, float maxWidth, const Font& font, EnableRoundingHacksOrNot enableRoundingHacks)
    198 {
    199     return truncateString(string, maxWidth, font, rightTruncateToBuffer, !enableRoundingHacks);
    200 }
    201 
    202 float StringTruncator::width(const String& string, const Font& font, EnableRoundingHacksOrNot enableRoundingHacks)
    203 {
    204     return stringWidth(font, string, !enableRoundingHacks);
    205 }
    206 
    207 } // namespace WebCore
    208