/*
* 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
#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"
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)
, 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
}
}
-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;
}
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