Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / platform / fonts / WidthIterator.cpp
index 8816fd8..5cb81ba 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2003, 2006, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
  * Copyright (C) 2008 Holger Hans Peter Freyther
+ * Copyright (C) 2014 Google Inc. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -23,6 +24,7 @@
 #include "platform/fonts/WidthIterator.h"
 
 #include "platform/fonts/Character.h"
+#include "platform/fonts/Font.h"
 #include "platform/fonts/FontPlatformFeatures.h"
 #include "platform/fonts/GlyphBuffer.h"
 #include "platform/fonts/Latin1TextIterator.h"
@@ -34,7 +36,7 @@ using namespace WTF;
 using namespace Unicode;
 using namespace std;
 
-namespace WebCore {
+namespace blink {
 
 WidthIterator::WidthIterator(const Font* font, const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts, bool accountForGlyphBounds, bool forTextEmphasis)
     : m_font(font)
@@ -42,14 +44,12 @@ WidthIterator::WidthIterator(const Font* font, const TextRun& run, HashSet<const
     , m_currentCharacter(0)
     , m_runWidthSoFar(0)
     , m_isAfterExpansion(!run.allowsLeadingExpansion())
-    , m_finalRoundingWidth(0)
-    , m_typesettingFeatures(font->fontDescription().typesettingFeatures())
     , m_fallbackFonts(fallbackFonts)
-    , m_accountForGlyphBounds(accountForGlyphBounds)
     , m_maxGlyphBoundingBoxY(numeric_limits<float>::min())
     , m_minGlyphBoundingBoxY(numeric_limits<float>::max())
     , m_firstGlyphOverflow(0)
     , m_lastGlyphOverflow(0)
+    , m_accountForGlyphBounds(accountForGlyphBounds)
     , m_forTextEmphasis(forTextEmphasis)
 {
     // If the padding is non-zero, count the number of spaces in the run
@@ -70,241 +70,167 @@ WidthIterator::WidthIterator(const Font* font, const TextRun& run, HashSet<const
     }
 }
 
-GlyphData WidthIterator::glyphDataForCharacter(UChar32 character, bool mirror, int currentCharacter, unsigned& advanceLength)
+GlyphData WidthIterator::glyphDataForCharacter(CharacterData& charData)
 {
     ASSERT(m_font);
 
 #if ENABLE(SVG_FONTS)
-    if (TextRun::RenderingContext* renderingContext = m_run.renderingContext())
-        return renderingContext->glyphDataForCharacter(*m_font, m_run, *this, character, mirror, currentCharacter, advanceLength);
+    if (TextRun::RenderingContext* renderingContext = m_run.renderingContext()) {
+        return renderingContext->glyphDataForCharacter(*m_font, m_run, *this, charData.character,
+            m_run.rtl(), charData.characterOffset, charData.clusterLength);
+    }
 #endif
 
-    return m_font->glyphDataForCharacter(character, mirror);
+    return m_font->glyphDataForCharacter(charData.character, m_run.rtl());
 }
 
-struct OriginalAdvancesForCharacterTreatedAsSpace {
-public:
-    OriginalAdvancesForCharacterTreatedAsSpace(bool isSpace, float advanceBefore, float advanceAt)
-        : characterIsSpace(isSpace)
-        , advanceBeforeCharacter(advanceBefore)
-        , advanceAtCharacter(advanceAt)
-    {
-    }
+float WidthIterator::characterWidth(UChar32 character, const GlyphData& glyphData) const
+{
+    const SimpleFontData* fontData = glyphData.fontData;
+    ASSERT(fontData);
 
-    bool characterIsSpace;
-    float advanceBeforeCharacter;
-    float advanceAtCharacter;
-};
+    if (UNLIKELY(character == '\t' && m_run.allowTabs()))
+        return m_font->tabWidth(*fontData, m_run.tabSize(), m_run.xPos() + m_runWidthSoFar);
 
-typedef Vector<pair<int, OriginalAdvancesForCharacterTreatedAsSpace>, 64> CharactersTreatedAsSpace;
+    float width = fontData->widthForGlyph(glyphData.glyph);
 
-static inline float applyFontTransforms(GlyphBuffer* glyphBuffer, unsigned& lastGlyphCount, TypesettingFeatures typesettingFeatures, CharactersTreatedAsSpace& charactersTreatedAsSpace)
-{
-    ASSERT(typesettingFeatures & (Kerning | Ligatures));
+    // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text.
+    if (UNLIKELY(m_run.horizontalGlyphStretch() != 1))
+        width *= m_run.horizontalGlyphStretch();
 
-    if (!glyphBuffer)
-        return 0;
+    return width;
+}
 
-    unsigned glyphBufferSize = glyphBuffer->size();
-    if (glyphBuffer->size() <= lastGlyphCount + 1)
-        return 0;
+void WidthIterator::cacheFallbackFont(UChar32 character, const SimpleFontData* fontData,
+    const SimpleFontData* primaryFont)
+{
+    if (fontData == primaryFont)
+        return;
+
+    // FIXME: This does a little extra work that could be avoided if
+    // glyphDataForCharacter() returned whether it chose to use a small caps font.
+    if (m_font->fontDescription().variant() == FontVariantNormal || character == toUpper(character)) {
+        m_fallbackFonts->add(fontData);
+    } else {
+        ASSERT(m_font->fontDescription().variant() == FontVariantSmallCaps);
+        const GlyphData uppercaseGlyphData = m_font->glyphDataForCharacter(toUpper(character),
+            m_run.rtl());
+        if (uppercaseGlyphData.fontData != primaryFont)
+            m_fallbackFonts->add(uppercaseGlyphData.fontData);
+    }
+}
+
+float WidthIterator::adjustSpacing(float width, const CharacterData& charData,
+    const SimpleFontData& fontData, GlyphBuffer* glyphBuffer)
+{
+    // Account for letter-spacing.
+    if (width)
+        width += m_font->fontDescription().letterSpacing();
+
+    static bool expandAroundIdeographs = FontPlatformFeatures::canExpandAroundIdeographsInComplexText();
+    bool treatAsSpace = Character::treatAsSpace(charData.character);
+    if (treatAsSpace || (expandAroundIdeographs && Character::isCJKIdeographOrSymbol(charData.character))) {
+        // Distribute the run's total expansion evenly over all expansion opportunities in the run.
+        if (m_expansion) {
+            if (!treatAsSpace && !m_isAfterExpansion) {
+                // Take the expansion opportunity before this ideograph.
+                m_expansion -= m_expansionPerOpportunity;
+                float expansionAtThisOpportunity = m_expansionPerOpportunity;
+                m_runWidthSoFar += expansionAtThisOpportunity;
+                if (glyphBuffer) {
+                    if (glyphBuffer->isEmpty()) {
+                        if (m_forTextEmphasis)
+                            glyphBuffer->add(fontData.zeroWidthSpaceGlyph(), &fontData, m_expansionPerOpportunity);
+                        else
+                            glyphBuffer->add(fontData.spaceGlyph(), &fontData, expansionAtThisOpportunity);
+                    } else {
+                        glyphBuffer->expandLastAdvance(expansionAtThisOpportunity);
+                    }
+                }
+            }
+            if (m_run.allowsTrailingExpansion()
+                || (m_run.ltr() && charData.characterOffset + charData.clusterLength < static_cast<size_t>(m_run.length()))
+                || (m_run.rtl() && charData.characterOffset)) {
+                m_expansion -= m_expansionPerOpportunity;
+                width += m_expansionPerOpportunity;
+                m_isAfterExpansion = true;
+            }
+        } else {
+            m_isAfterExpansion = false;
+        }
 
-    FloatSize* advances = glyphBuffer->advances(0);
-    float widthDifference = 0;
-    for (unsigned i = lastGlyphCount; i < glyphBufferSize; ++i)
-        widthDifference -= advances[i].width();
-
-    for (size_t i = 0; i < charactersTreatedAsSpace.size(); ++i) {
-        int spaceOffset = charactersTreatedAsSpace[i].first;
-        const OriginalAdvancesForCharacterTreatedAsSpace& originalAdvances = charactersTreatedAsSpace[i].second;
-        if (spaceOffset && !originalAdvances.characterIsSpace)
-            glyphBuffer->advances(spaceOffset - 1)->setWidth(originalAdvances.advanceBeforeCharacter);
-        glyphBuffer->advances(spaceOffset)->setWidth(originalAdvances.advanceAtCharacter);
+        // Account for word spacing.
+        // We apply additional space between "words" by adding width to the space character.
+        if (treatAsSpace && (charData.character != '\t' || !m_run.allowTabs())
+            && (charData.characterOffset || charData.character == noBreakSpace)
+            && m_font->fontDescription().wordSpacing()) {
+            width += m_font->fontDescription().wordSpacing();
+        }
+    } else {
+        m_isAfterExpansion = false;
     }
-    charactersTreatedAsSpace.clear();
 
-    for (unsigned i = lastGlyphCount; i < glyphBufferSize; ++i)
-        widthDifference += advances[i].width();
+    return width;
+}
 
-    lastGlyphCount = glyphBufferSize;
-    return widthDifference;
+void WidthIterator::updateGlyphBounds(const GlyphData& glyphData, float width, bool firstCharacter)
+{
+    ASSERT(glyphData.fontData);
+    FloatRect bounds = glyphData.fontData->boundsForGlyph(glyphData.glyph);
+
+    if (firstCharacter)
+        m_firstGlyphOverflow = max<float>(0, -bounds.x());
+    m_lastGlyphOverflow = max<float>(0, bounds.maxX() - width);
+    m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, bounds.maxY());
+    m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, bounds.y());
 }
 
 template <typename TextIterator>
-inline unsigned WidthIterator::advanceInternal(TextIterator& textIterator, GlyphBuffer* glyphBuffer)
+unsigned WidthIterator::advanceInternal(TextIterator& textIterator, GlyphBuffer* glyphBuffer)
 {
-    bool rtl = m_run.rtl();
-    bool hasExtraSpacing = (m_font->fontDescription().letterSpacing() || m_font->fontDescription().wordSpacing() || m_expansion) && !m_run.spacingDisabled();
-
-    float widthSinceLastRounding = m_runWidthSoFar;
-    m_runWidthSoFar = floorf(m_runWidthSoFar);
-    widthSinceLastRounding -= m_runWidthSoFar;
-
-    float lastRoundingWidth = m_finalRoundingWidth;
-    FloatRect bounds;
+    bool hasExtraSpacing = (m_font->fontDescription().letterSpacing() || m_font->fontDescription().wordSpacing() || m_expansion)
+        && !m_run.spacingDisabled();
 
     const SimpleFontData* primaryFont = m_font->primaryFont();
     const SimpleFontData* lastFontData = primaryFont;
-    unsigned lastGlyphCount = glyphBuffer ? glyphBuffer->size() : 0;
-
-    UChar32 character = 0;
-    unsigned clusterLength = 0;
-    CharactersTreatedAsSpace charactersTreatedAsSpace;
-    while (textIterator.consume(character, clusterLength)) {
-        unsigned advanceLength = clusterLength;
-        const GlyphData& glyphData = glyphDataForCharacter(character, rtl, textIterator.currentCharacter(), advanceLength);
+
+    CharacterData charData;
+    while (textIterator.consume(charData.character, charData.clusterLength)) {
+        charData.characterOffset = textIterator.currentCharacter();
+
+        const GlyphData glyphData = glyphDataForCharacter(charData);
         Glyph glyph = glyphData.glyph;
         const SimpleFontData* fontData = glyphData.fontData;
-
         ASSERT(fontData);
 
         // Now that we have a glyph and font data, get its width.
-        float width;
-        if (character == '\t' && m_run.allowTabs())
-            width = m_font->tabWidth(*fontData, m_run.tabSize(), m_run.xPos() + m_runWidthSoFar + widthSinceLastRounding);
-        else {
-            width = fontData->widthForGlyph(glyph);
-
-            // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text.
-            width *= m_run.horizontalGlyphStretch();
-
-            // We special case spaces in two ways when applying word rounding.
-            // First, we round spaces to an adjusted width in all fonts.
-            // Second, in fixed-pitch fonts we ensure that all characters that
-            // match the width of the space character have the same width as the space character.
-            if (m_run.applyWordRounding() && width == fontData->spaceWidth() && (fontData->pitch() == FixedPitch || glyph == fontData->spaceGlyph()))
-                width = fontData->adjustedSpaceWidth();
-        }
-
-        if (fontData != lastFontData && width) {
-            if (shouldApplyFontTransforms())
-                m_runWidthSoFar += applyFontTransforms(glyphBuffer, lastGlyphCount, m_typesettingFeatures, charactersTreatedAsSpace);
+        float width = characterWidth(charData.character, glyphData);
 
+        if (m_fallbackFonts && lastFontData != fontData && width) {
             lastFontData = fontData;
-            if (m_fallbackFonts && fontData != primaryFont) {
-                // FIXME: This does a little extra work that could be avoided if
-                // glyphDataForCharacter() returned whether it chose to use a small caps font.
-                if (m_font->fontDescription().variant() == FontVariantNormal || character == toUpper(character))
-                    m_fallbackFonts->add(fontData);
-                else {
-                    ASSERT(m_font->fontDescription().variant() == FontVariantSmallCaps);
-                    const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(toUpper(character), rtl);
-                    if (uppercaseGlyphData.fontData != primaryFont)
-                        m_fallbackFonts->add(uppercaseGlyphData.fontData);
-                }
-            }
+            cacheFallbackFont(charData.character, fontData, primaryFont);
         }
 
-        if (hasExtraSpacing) {
-            // Account for letter-spacing.
-            if (width && m_font->fontDescription().letterSpacing())
-                width += m_font->fontDescription().letterSpacing();
-
-            static bool expandAroundIdeographs = FontPlatformFeatures::canExpandAroundIdeographsInComplexText();
-            bool treatAsSpace = Character::treatAsSpace(character);
-            if (treatAsSpace || (expandAroundIdeographs && Character::isCJKIdeographOrSymbol(character))) {
-                // Distribute the run's total expansion evenly over all expansion opportunities in the run.
-                if (m_expansion) {
-                    float previousExpansion = m_expansion;
-                    if (!treatAsSpace && !m_isAfterExpansion) {
-                        // Take the expansion opportunity before this ideograph.
-                        m_expansion -= m_expansionPerOpportunity;
-                        float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
-                        m_runWidthSoFar += expansionAtThisOpportunity;
-                        if (glyphBuffer) {
-                            if (glyphBuffer->isEmpty()) {
-                                if (m_forTextEmphasis)
-                                    glyphBuffer->add(fontData->zeroWidthSpaceGlyph(), fontData, m_expansionPerOpportunity);
-                                else
-                                    glyphBuffer->add(fontData->spaceGlyph(), fontData, expansionAtThisOpportunity);
-                            } else
-                                glyphBuffer->expandLastAdvance(expansionAtThisOpportunity);
-                        }
-                        previousExpansion = m_expansion;
-                    }
-                    if (m_run.allowsTrailingExpansion() || (m_run.ltr() && textIterator.currentCharacter() + advanceLength < static_cast<size_t>(m_run.length()))
-                        || (m_run.rtl() && textIterator.currentCharacter())) {
-                        m_expansion -= m_expansionPerOpportunity;
-                        width += !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
-                        m_isAfterExpansion = true;
-                    }
-                } else
-                    m_isAfterExpansion = false;
-
-                // Account for word spacing.
-                // We apply additional space between "words" by adding width to the space character.
-                if (treatAsSpace && (character != '\t' || !m_run.allowTabs()) && (textIterator.currentCharacter() || character == noBreakSpace) && m_font->fontDescription().wordSpacing())
-                    width += m_font->fontDescription().wordSpacing();
-            } else
-                m_isAfterExpansion = false;
-        }
-
-        if (shouldApplyFontTransforms() && glyphBuffer && Character::treatAsSpace(character)) {
-            charactersTreatedAsSpace.append(make_pair(glyphBuffer->size(),
-                OriginalAdvancesForCharacterTreatedAsSpace(character == ' ',
-                    glyphBuffer->size() ? glyphBuffer->advanceAt(glyphBuffer->size() - 1).width() : 0,
-                    width)));
-        }
+        if (hasExtraSpacing)
+            width = adjustSpacing(width, charData, *fontData, glyphBuffer);
 
-        if (m_accountForGlyphBounds) {
-            bounds = fontData->boundsForGlyph(glyph);
-            if (!textIterator.currentCharacter())
-                m_firstGlyphOverflow = max<float>(0, -bounds.x());
-        }
+        if (m_accountForGlyphBounds)
+            updateGlyphBounds(glyphData, width, !charData.characterOffset);
 
-        if (m_forTextEmphasis && !Character::canReceiveTextEmphasis(character))
+        if (m_forTextEmphasis && !Character::canReceiveTextEmphasis(charData.character))
             glyph = 0;
 
         // Advance past the character we just dealt with.
-        textIterator.advance(advanceLength);
-
-        float oldWidth = width;
-
-        // Force characters that are used to determine word boundaries for the rounding hack
-        // to be integer width, so following words will start on an integer boundary.
-        if (m_run.applyWordRounding() && Character::isRoundingHackCharacter(character)) {
-            width = ceilf(width);
-
-            // Since widthSinceLastRounding can lose precision if we include measurements for
-            // preceding whitespace, we bypass it here.
-            m_runWidthSoFar += width;
-
-            // Since this is a rounding hack character, we should have reset this sum on the previous
-            // iteration.
-            ASSERT(!widthSinceLastRounding);
-        } else {
-            // Check to see if the next character is a "rounding hack character", if so, adjust
-            // width so that the total run width will be on an integer boundary.
-            if ((m_run.applyWordRounding() && textIterator.currentCharacter() < m_run.length() && Character::isRoundingHackCharacter(*(textIterator.characters())))
-                || (m_run.applyRunRounding() && textIterator.currentCharacter() >= m_run.length())) {
-                float totalWidth = widthSinceLastRounding + width;
-                widthSinceLastRounding = ceilf(totalWidth);
-                width += widthSinceLastRounding - totalWidth;
-                m_runWidthSoFar += widthSinceLastRounding;
-                widthSinceLastRounding = 0;
-            } else
-                widthSinceLastRounding += width;
-        }
+        textIterator.advance(charData.clusterLength);
+        m_runWidthSoFar += width;
 
         if (glyphBuffer)
-            glyphBuffer->add(glyph, fontData, (rtl ? oldWidth + lastRoundingWidth : width));
-
-        lastRoundingWidth = width - oldWidth;
-
-        if (m_accountForGlyphBounds) {
-            m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, bounds.maxY());
-            m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, bounds.y());
-            m_lastGlyphOverflow = max<float>(0, bounds.maxX() - width);
-        }
+            glyphBuffer->add(glyph, fontData, width);
     }
 
-    if (shouldApplyFontTransforms())
-        m_runWidthSoFar += applyFontTransforms(glyphBuffer, lastGlyphCount, m_typesettingFeatures, charactersTreatedAsSpace);
-
     unsigned consumedCharacters = textIterator.currentCharacter() - m_currentCharacter;
     m_currentCharacter = textIterator.currentCharacter();
-    m_runWidthSoFar += widthSinceLastRounding;
-    m_finalRoundingWidth = lastRoundingWidth;
+
     return consumedCharacters;
 }
 
@@ -327,15 +253,15 @@ unsigned WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer)
     return advanceInternal(textIterator, glyphBuffer);
 }
 
-bool WidthIterator::advanceOneCharacter(float& width, GlyphBuffer& glyphBuffer)
+bool WidthIterator::advanceOneCharacter(float& width)
 {
-    unsigned oldSize = glyphBuffer.size();
-    advance(m_currentCharacter + 1, &glyphBuffer);
-    float w = 0;
-    for (unsigned i = oldSize; i < glyphBuffer.size(); ++i)
-        w += glyphBuffer.advanceAt(i).width();
-    width = w;
-    return glyphBuffer.size() > oldSize;
-}
+    float initialWidth = m_runWidthSoFar;
+
+    if (!advance(m_currentCharacter + 1))
+        return false;
 
+    width = m_runWidthSoFar - initialWidth;
+    return true;
 }
+
+} // namespace blink