Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2003, 2006, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
      3  * Copyright (C) 2008 Holger Hans Peter Freyther
      4  *
      5  * This library is free software; you can redistribute it and/or
      6  * modify it under the terms of the GNU Library General Public
      7  * License as published by the Free Software Foundation; either
      8  * version 2 of the License, or (at your option) any later version.
      9  *
     10  * This library is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13  * Library General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU Library General Public License
     16  * along with this library; see the file COPYING.LIB.  If not, write to
     17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18  * Boston, MA 02110-1301, USA.
     19  *
     20  */
     21 
     22 #include "config.h"
     23 #include "WidthIterator.h"
     24 
     25 #include "Font.h"
     26 #include "GlyphBuffer.h"
     27 #include "SimpleFontData.h"
     28 #include "TextRun.h"
     29 #include <wtf/MathExtras.h>
     30 
     31 #if USE(ICU_UNICODE)
     32 #include <unicode/unorm.h>
     33 #endif
     34 
     35 using namespace WTF;
     36 using namespace Unicode;
     37 using namespace std;
     38 
     39 namespace WebCore {
     40 
     41 // According to http://www.unicode.org/Public/UNIDATA/UCD.html#Canonical_Combining_Class_Values
     42 static const uint8_t hiraganaKatakanaVoicingMarksCombiningClass = 8;
     43 
     44 WidthIterator::WidthIterator(const Font* font, const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts, bool accountForGlyphBounds, bool forTextEmphasis)
     45     : m_font(font)
     46     , m_run(run)
     47     , m_end(run.length())
     48     , m_currentCharacter(0)
     49     , m_runWidthSoFar(0)
     50     , m_isAfterExpansion(!run.allowsLeadingExpansion())
     51     , m_fallbackFonts(fallbackFonts)
     52     , m_accountForGlyphBounds(accountForGlyphBounds)
     53     , m_maxGlyphBoundingBoxY(numeric_limits<float>::min())
     54     , m_minGlyphBoundingBoxY(numeric_limits<float>::max())
     55     , m_firstGlyphOverflow(0)
     56     , m_lastGlyphOverflow(0)
     57     , m_forTextEmphasis(forTextEmphasis)
     58 {
     59     // If the padding is non-zero, count the number of spaces in the run
     60     // and divide that by the padding for per space addition.
     61     m_expansion = m_run.expansion();
     62     if (!m_expansion)
     63         m_expansionPerOpportunity = 0;
     64     else {
     65         bool isAfterExpansion = m_isAfterExpansion;
     66         unsigned expansionOpportunityCount = Font::expansionOpportunityCount(m_run.characters(), m_end, m_run.ltr() ? LTR : RTL, isAfterExpansion);
     67         if (isAfterExpansion && !m_run.allowsTrailingExpansion())
     68             expansionOpportunityCount--;
     69 
     70         if (!expansionOpportunityCount)
     71             m_expansionPerOpportunity = 0;
     72         else
     73             m_expansionPerOpportunity = m_expansion / expansionOpportunityCount;
     74     }
     75 }
     76 
     77 void WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer)
     78 {
     79     if (offset > m_end)
     80         offset = m_end;
     81 
     82     int currentCharacter = m_currentCharacter;
     83     const UChar* cp = m_run.data(currentCharacter);
     84 
     85     bool rtl = m_run.rtl();
     86     bool hasExtraSpacing = (m_font->letterSpacing() || m_font->wordSpacing() || m_expansion) && !m_run.spacingDisabled();
     87 
     88     FloatRect bounds;
     89 
     90     const SimpleFontData* primaryFont = m_font->primaryFont();
     91     const SimpleFontData* lastFontData = primaryFont;
     92 
     93     while (currentCharacter < offset) {
     94         UChar32 c = *cp;
     95         unsigned clusterLength = 1;
     96         if (c >= 0x3041) {
     97             if (c <= 0x30FE) {
     98                 // Deal with Hiragana and Katakana voiced and semi-voiced syllables.
     99                 // Normalize into composed form, and then look for glyph with base + combined mark.
    100                 // Check above for character range to minimize performance impact.
    101                 UChar32 normalized = normalizeVoicingMarks(currentCharacter);
    102                 if (normalized) {
    103                     c = normalized;
    104                     clusterLength = 2;
    105                 }
    106             } else if (U16_IS_SURROGATE(c)) {
    107                 if (!U16_IS_SURROGATE_LEAD(c))
    108                     break;
    109 
    110                 // Do we have a surrogate pair?  If so, determine the full Unicode (32 bit)
    111                 // code point before glyph lookup.
    112                 // Make sure we have another character and it's a low surrogate.
    113                 if (currentCharacter + 1 >= m_run.length())
    114                     break;
    115                 UChar low = cp[1];
    116                 if (!U16_IS_TRAIL(low))
    117                     break;
    118                 c = U16_GET_SUPPLEMENTARY(c, low);
    119                 clusterLength = 2;
    120             }
    121         }
    122 
    123         const GlyphData& glyphData = m_font->glyphDataForCharacter(c, rtl);
    124         Glyph glyph = glyphData.glyph;
    125         const SimpleFontData* fontData = glyphData.fontData;
    126 
    127         ASSERT(fontData);
    128 
    129         // Now that we have a glyph and font data, get its width.
    130         float width;
    131         if (c == '\t' && m_run.allowTabs()) {
    132             float tabWidth = m_font->tabWidth(*fontData);
    133             width = tabWidth - fmodf(m_run.xPos() + m_runWidthSoFar, tabWidth);
    134         } else {
    135             width = fontData->widthForGlyph(glyph);
    136 
    137 #if ENABLE(SVG)
    138             // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text.
    139             width *= m_run.horizontalGlyphStretch();
    140 #endif
    141         }
    142 
    143         if (fontData != lastFontData && width) {
    144             lastFontData = fontData;
    145             if (m_fallbackFonts && fontData != primaryFont) {
    146                 // FIXME: This does a little extra work that could be avoided if
    147                 // glyphDataForCharacter() returned whether it chose to use a small caps font.
    148                 if (!m_font->isSmallCaps() || c == toUpper(c))
    149                     m_fallbackFonts->add(fontData);
    150                 else {
    151                     const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(toUpper(c), rtl);
    152                     if (uppercaseGlyphData.fontData != primaryFont)
    153                         m_fallbackFonts->add(uppercaseGlyphData.fontData);
    154                 }
    155             }
    156         }
    157 
    158         if (hasExtraSpacing) {
    159             // Account for letter-spacing.
    160             if (width && m_font->letterSpacing())
    161                 width += m_font->letterSpacing();
    162 
    163             static bool expandAroundIdeographs = Font::canExpandAroundIdeographsInComplexText();
    164             bool treatAsSpace = Font::treatAsSpace(c);
    165             if (treatAsSpace || (expandAroundIdeographs && Font::isCJKIdeographOrSymbol(c))) {
    166                 // Distribute the run's total expansion evenly over all expansion opportunities in the run.
    167                 if (m_expansion) {
    168                     if (!treatAsSpace && !m_isAfterExpansion) {
    169                         // Take the expansion opportunity before this ideograph.
    170                         m_expansion -= m_expansionPerOpportunity;
    171                         m_runWidthSoFar += m_expansionPerOpportunity;
    172                         if (glyphBuffer) {
    173                             if (glyphBuffer->isEmpty())
    174                                 glyphBuffer->add(fontData->spaceGlyph(), fontData, m_expansionPerOpportunity);
    175                             else
    176                                 glyphBuffer->expandLastAdvance(m_expansionPerOpportunity);
    177                         }
    178                     }
    179                     if (m_run.allowsTrailingExpansion() || (m_run.ltr() && currentCharacter + clusterLength < static_cast<size_t>(m_run.length()))
    180                         || (m_run.rtl() && currentCharacter)) {
    181                         m_expansion -= m_expansionPerOpportunity;
    182                         width += m_expansionPerOpportunity;
    183                         m_isAfterExpansion = true;
    184                     }
    185                 } else
    186                     m_isAfterExpansion = false;
    187 
    188                 // Account for word spacing.
    189                 // We apply additional space between "words" by adding width to the space character.
    190                 if (treatAsSpace && currentCharacter && !Font::treatAsSpace(cp[-1]) && m_font->wordSpacing())
    191                     width += m_font->wordSpacing();
    192             } else
    193                 m_isAfterExpansion = false;
    194         }
    195 
    196         if (m_accountForGlyphBounds) {
    197             bounds = fontData->boundsForGlyph(glyph);
    198             if (!currentCharacter)
    199                 m_firstGlyphOverflow = max<float>(0, -bounds.x());
    200         }
    201 
    202         if (m_forTextEmphasis && !Font::canReceiveTextEmphasis(c))
    203             glyph = 0;
    204 
    205         // Advance past the character we just dealt with.
    206         cp += clusterLength;
    207         currentCharacter += clusterLength;
    208 
    209         m_runWidthSoFar += width;
    210 
    211         if (glyphBuffer)
    212             glyphBuffer->add(glyph, fontData, width);
    213 
    214         if (m_accountForGlyphBounds) {
    215             m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, bounds.maxY());
    216             m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, bounds.y());
    217             m_lastGlyphOverflow = max<float>(0, bounds.maxX() - width);
    218         }
    219     }
    220 
    221     m_currentCharacter = currentCharacter;
    222 }
    223 
    224 bool WidthIterator::advanceOneCharacter(float& width, GlyphBuffer* glyphBuffer)
    225 {
    226     int oldSize = glyphBuffer->size();
    227     advance(m_currentCharacter + 1, glyphBuffer);
    228     float w = 0;
    229     for (int i = oldSize; i < glyphBuffer->size(); ++i)
    230         w += glyphBuffer->advanceAt(i);
    231     width = w;
    232     return glyphBuffer->size() > oldSize;
    233 }
    234 
    235 UChar32 WidthIterator::normalizeVoicingMarks(int currentCharacter)
    236 {
    237     if (currentCharacter + 1 < m_end) {
    238         if (combiningClass(m_run[currentCharacter + 1]) == hiraganaKatakanaVoicingMarksCombiningClass) {
    239 #if USE(ICU_UNICODE)
    240             // Normalize into composed form using 3.2 rules.
    241             UChar normalizedCharacters[2] = { 0, 0 };
    242             UErrorCode uStatus = U_ZERO_ERROR;
    243             int32_t resultLength = unorm_normalize(m_run.data(currentCharacter), 2,
    244                 UNORM_NFC, UNORM_UNICODE_3_2, &normalizedCharacters[0], 2, &uStatus);
    245             if (resultLength == 1 && uStatus == 0)
    246                 return normalizedCharacters[0];
    247 #elif USE(QT4_UNICODE)
    248             QString tmp(reinterpret_cast<const QChar*>(m_run.data(currentCharacter)), 2);
    249             QString res = tmp.normalized(QString::NormalizationForm_C, QChar::Unicode_3_2);
    250             if (res.length() == 1)
    251                 return res.at(0).unicode();
    252 #endif
    253         }
    254     }
    255     return 0;
    256 }
    257 
    258 }
    259