Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2003, 2006, 2008 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 <wtf/MathExtras.h>
     29 
     30 #if USE(ICU_UNICODE)
     31 #include <unicode/unorm.h>
     32 #endif
     33 
     34 using namespace WTF;
     35 using namespace Unicode;
     36 
     37 namespace WebCore {
     38 
     39 // According to http://www.unicode.org/Public/UNIDATA/UCD.html#Canonical_Combining_Class_Values
     40 static const uint8_t hiraganaKatakanaVoicingMarksCombiningClass = 8;
     41 
     42 WidthIterator::WidthIterator(const Font* font, const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts)
     43     : m_font(font)
     44     , m_run(run)
     45     , m_end(run.length())
     46     , m_currentCharacter(0)
     47     , m_runWidthSoFar(0)
     48     , m_finalRoundingWidth(0)
     49     , m_fallbackFonts(fallbackFonts)
     50 {
     51     // If the padding is non-zero, count the number of spaces in the run
     52     // and divide that by the padding for per space addition.
     53     m_padding = m_run.padding();
     54     if (!m_padding)
     55         m_padPerSpace = 0;
     56     else {
     57         float numSpaces = 0;
     58         for (int i = 0; i < run.length(); i++)
     59             if (Font::treatAsSpace(m_run[i]))
     60                 numSpaces++;
     61 
     62         if (numSpaces == 0)
     63             m_padPerSpace = 0;
     64         else
     65             m_padPerSpace = ceilf(m_run.padding() / numSpaces);
     66     }
     67 }
     68 
     69 void WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer)
     70 {
     71     if (offset > m_end)
     72         offset = m_end;
     73 
     74     int currentCharacter = m_currentCharacter;
     75     const UChar* cp = m_run.data(currentCharacter);
     76 
     77     bool rtl = m_run.rtl();
     78     bool hasExtraSpacing = (m_font->letterSpacing() || m_font->wordSpacing() || m_padding) && !m_run.spacingDisabled();
     79 
     80     float runWidthSoFar = m_runWidthSoFar;
     81     float lastRoundingWidth = m_finalRoundingWidth;
     82 
     83     const SimpleFontData* primaryFont = m_font->primaryFont();
     84     const SimpleFontData* lastFontData = primaryFont;
     85 
     86     while (currentCharacter < offset) {
     87         UChar32 c = *cp;
     88         unsigned clusterLength = 1;
     89         if (c >= 0x3041) {
     90             if (c <= 0x30FE) {
     91                 // Deal with Hiragana and Katakana voiced and semi-voiced syllables.
     92                 // Normalize into composed form, and then look for glyph with base + combined mark.
     93                 // Check above for character range to minimize performance impact.
     94                 UChar32 normalized = normalizeVoicingMarks(currentCharacter);
     95                 if (normalized) {
     96                     c = normalized;
     97                     clusterLength = 2;
     98                 }
     99             } else if (U16_IS_SURROGATE(c)) {
    100                 if (!U16_IS_SURROGATE_LEAD(c))
    101                     break;
    102 
    103                 // Do we have a surrogate pair?  If so, determine the full Unicode (32 bit)
    104                 // code point before glyph lookup.
    105                 // Make sure we have another character and it's a low surrogate.
    106                 if (currentCharacter + 1 >= m_run.length())
    107                     break;
    108                 UChar low = cp[1];
    109                 if (!U16_IS_TRAIL(low))
    110                     break;
    111                 c = U16_GET_SUPPLEMENTARY(c, low);
    112                 clusterLength = 2;
    113             }
    114         }
    115 
    116         const GlyphData& glyphData = m_font->glyphDataForCharacter(c, rtl);
    117         Glyph glyph = glyphData.glyph;
    118         const SimpleFontData* fontData = glyphData.fontData;
    119 
    120         ASSERT(fontData);
    121 
    122         // Now that we have a glyph and font data, get its width.
    123         float width;
    124         if (c == '\t' && m_run.allowTabs()) {
    125             float tabWidth = m_font->tabWidth();
    126             width = tabWidth - fmodf(m_run.xPos() + runWidthSoFar, tabWidth);
    127         } else {
    128             width = fontData->widthForGlyph(glyph);
    129             // We special case spaces in two ways when applying word rounding.
    130             // First, we round spaces to an adjusted width in all fonts.
    131             // Second, in fixed-pitch fonts we ensure that all characters that
    132             // match the width of the space character have the same width as the space character.
    133             if (width == fontData->spaceWidth() && (fontData->pitch() == FixedPitch || glyph == fontData->spaceGlyph()) && m_run.applyWordRounding())
    134                 width = fontData->adjustedSpaceWidth();
    135         }
    136 
    137         if (fontData != lastFontData && width) {
    138             lastFontData = fontData;
    139             if (m_fallbackFonts && fontData != primaryFont) {
    140                 // FIXME: This does a little extra work that could be avoided if
    141                 // glyphDataForCharacter() returned whether it chose to use a small caps font.
    142                 if (!m_font->isSmallCaps() || c == toUpper(c))
    143                     m_fallbackFonts->add(fontData);
    144                 else {
    145                     const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(toUpper(c), rtl);
    146                     if (uppercaseGlyphData.fontData != primaryFont)
    147                         m_fallbackFonts->add(uppercaseGlyphData.fontData);
    148                 }
    149             }
    150         }
    151 
    152         if (hasExtraSpacing) {
    153             // Account for letter-spacing.
    154             if (width && m_font->letterSpacing())
    155                 width += m_font->letterSpacing();
    156 
    157             if (Font::treatAsSpace(c)) {
    158                 // Account for padding. WebCore uses space padding to justify text.
    159                 // We distribute the specified padding over the available spaces in the run.
    160                 if (m_padding) {
    161                     // Use left over padding if not evenly divisible by number of spaces.
    162                     if (m_padding < m_padPerSpace) {
    163                         width += m_padding;
    164                         m_padding = 0;
    165                     } else {
    166                         width += m_padPerSpace;
    167                         m_padding -= m_padPerSpace;
    168                     }
    169                 }
    170 
    171                 // Account for word spacing.
    172                 // We apply additional space between "words" by adding width to the space character.
    173                 if (currentCharacter != 0 && !Font::treatAsSpace(cp[-1]) && m_font->wordSpacing())
    174                     width += m_font->wordSpacing();
    175             }
    176         }
    177 
    178         // Advance past the character we just dealt with.
    179         cp += clusterLength;
    180         currentCharacter += clusterLength;
    181 
    182         // Account for float/integer impedance mismatch between CG and KHTML. "Words" (characters
    183         // followed by a character defined by isRoundingHackCharacter()) are always an integer width.
    184         // We adjust the width of the last character of a "word" to ensure an integer width.
    185         // If we move KHTML to floats we can remove this (and related) hacks.
    186 
    187         float oldWidth = width;
    188 
    189         // Force characters that are used to determine word boundaries for the rounding hack
    190         // to be integer width, so following words will start on an integer boundary.
    191         if (m_run.applyWordRounding() && Font::isRoundingHackCharacter(c))
    192             width = ceilf(width);
    193 
    194         // Check to see if the next character is a "rounding hack character", if so, adjust
    195         // width so that the total run width will be on an integer boundary.
    196         if ((m_run.applyWordRounding() && currentCharacter < m_run.length() && Font::isRoundingHackCharacter(*cp))
    197                 || (m_run.applyRunRounding() && currentCharacter >= m_end)) {
    198             float totalWidth = runWidthSoFar + width;
    199             width += ceilf(totalWidth) - totalWidth;
    200         }
    201 
    202         runWidthSoFar += width;
    203 
    204         if (glyphBuffer)
    205             glyphBuffer->add(glyph, fontData, (rtl ? oldWidth + lastRoundingWidth : width));
    206 
    207         lastRoundingWidth = width - oldWidth;
    208     }
    209 
    210     m_currentCharacter = currentCharacter;
    211     m_runWidthSoFar = runWidthSoFar;
    212     m_finalRoundingWidth = lastRoundingWidth;
    213 }
    214 
    215 bool WidthIterator::advanceOneCharacter(float& width, GlyphBuffer* glyphBuffer)
    216 {
    217     glyphBuffer->clear();
    218     advance(m_currentCharacter + 1, glyphBuffer);
    219     float w = 0;
    220     for (int i = 0; i < glyphBuffer->size(); ++i)
    221         w += glyphBuffer->advanceAt(i);
    222     width = w;
    223     return !glyphBuffer->isEmpty();
    224 }
    225 
    226 UChar32 WidthIterator::normalizeVoicingMarks(int currentCharacter)
    227 {
    228     if (currentCharacter + 1 < m_end) {
    229         if (combiningClass(m_run[currentCharacter + 1]) == hiraganaKatakanaVoicingMarksCombiningClass) {
    230 #if USE(ICU_UNICODE)
    231             // Normalize into composed form using 3.2 rules.
    232             UChar normalizedCharacters[2] = { 0, 0 };
    233             UErrorCode uStatus = U_ZERO_ERROR;
    234             int32_t resultLength = unorm_normalize(m_run.data(currentCharacter), 2,
    235                 UNORM_NFC, UNORM_UNICODE_3_2, &normalizedCharacters[0], 2, &uStatus);
    236             if (resultLength == 1 && uStatus == 0)
    237                 return normalizedCharacters[0];
    238 #elif USE(QT4_UNICODE)
    239             QString tmp(reinterpret_cast<const QChar*>(m_run.data(currentCharacter)), 2);
    240             QString res = tmp.normalized(QString::NormalizationForm_C, QChar::Unicode_3_2);
    241             if (res.length() == 1)
    242                 return res.at(0).unicode();
    243 #endif
    244         }
    245     }
    246     return 0;
    247 }
    248 
    249 }
    250