|  | /* | 
|  | * 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 | 
|  | * License as published by the Free Software Foundation; either | 
|  | * version 2 of the License, or (at your option) any later version. | 
|  | * | 
|  | * This library is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | * Library General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU Library General Public License | 
|  | * along with this library; see the file COPYING.LIB.  If not, write to | 
|  | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 
|  | * Boston, MA 02110-1301, USA. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include "sky/engine/config.h" | 
|  | #include "sky/engine/platform/fonts/WidthIterator.h" | 
|  |  | 
|  | #include "sky/engine/platform/fonts/Character.h" | 
|  | #include "sky/engine/platform/fonts/Font.h" | 
|  | #include "sky/engine/platform/fonts/FontPlatformFeatures.h" | 
|  | #include "sky/engine/platform/fonts/GlyphBuffer.h" | 
|  | #include "sky/engine/platform/fonts/Latin1TextIterator.h" | 
|  | #include "sky/engine/platform/fonts/SimpleFontData.h" | 
|  | #include "sky/engine/platform/text/SurrogatePairAwareTextIterator.h" | 
|  | #include "sky/engine/wtf/MathExtras.h" | 
|  |  | 
|  | using namespace WTF; | 
|  | using namespace Unicode; | 
|  |  | 
|  | namespace blink { | 
|  |  | 
|  | WidthIterator::WidthIterator(const Font* font, const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts, bool accountForGlyphBounds, bool forTextEmphasis) | 
|  | : m_font(font) | 
|  | , m_run(run) | 
|  | , m_currentCharacter(0) | 
|  | , m_runWidthSoFar(0) | 
|  | , m_isAfterExpansion(!run.allowsLeadingExpansion()) | 
|  | , m_fallbackFonts(fallbackFonts) | 
|  | , m_maxGlyphBoundingBoxY(std::numeric_limits<float>::min()) | 
|  | , m_minGlyphBoundingBoxY(std::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 | 
|  | // and divide that by the padding for per space addition. | 
|  | m_expansion = m_run.expansion(); | 
|  | if (!m_expansion) | 
|  | m_expansionPerOpportunity = 0; | 
|  | else { | 
|  | bool isAfterExpansion = m_isAfterExpansion; | 
|  | unsigned expansionOpportunityCount = m_run.is8Bit() ? Character::expansionOpportunityCount(m_run.characters8(), m_run.length(), m_run.direction(), isAfterExpansion) : Character::expansionOpportunityCount(m_run.characters16(), m_run.length(), m_run.direction(), isAfterExpansion); | 
|  | if (isAfterExpansion && !m_run.allowsTrailingExpansion()) | 
|  | expansionOpportunityCount--; | 
|  |  | 
|  | if (!expansionOpportunityCount) | 
|  | m_expansionPerOpportunity = 0; | 
|  | else | 
|  | m_expansionPerOpportunity = m_expansion / expansionOpportunityCount; | 
|  | } | 
|  | } | 
|  |  | 
|  | GlyphData WidthIterator::glyphDataForCharacter(CharacterData& charData) | 
|  | { | 
|  | ASSERT(m_font); | 
|  | return m_font->glyphDataForCharacter(charData.character, m_run.rtl()); | 
|  | } | 
|  |  | 
|  | float WidthIterator::characterWidth(UChar32 character, const GlyphData& glyphData) const | 
|  | { | 
|  | const SimpleFontData* fontData = glyphData.fontData; | 
|  | ASSERT(fontData); | 
|  |  | 
|  | if (UNLIKELY(character == '\t' && m_run.allowTabs())) | 
|  | return m_font->tabWidth(*fontData, m_run.tabSize(), m_run.xPos() + m_runWidthSoFar); | 
|  |  | 
|  | float width = fontData->widthForGlyph(glyphData.glyph); | 
|  |  | 
|  | // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text. | 
|  | if (UNLIKELY(m_run.horizontalGlyphStretch() != 1)) | 
|  | width *= m_run.horizontalGlyphStretch(); | 
|  |  | 
|  | return width; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | // 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; | 
|  | } | 
|  |  | 
|  | return width; | 
|  | } | 
|  |  | 
|  | void WidthIterator::updateGlyphBounds(const GlyphData& glyphData, float width, bool firstCharacter) | 
|  | { | 
|  | ASSERT(glyphData.fontData); | 
|  | FloatRect bounds = glyphData.fontData->boundsForGlyph(glyphData.glyph); | 
|  |  | 
|  | if (firstCharacter) | 
|  | m_firstGlyphOverflow = std::max<float>(0, -bounds.x()); | 
|  | m_lastGlyphOverflow = std::max<float>(0, bounds.maxX() - width); | 
|  | m_maxGlyphBoundingBoxY = std::max(m_maxGlyphBoundingBoxY, bounds.maxY()); | 
|  | m_minGlyphBoundingBoxY = std::min(m_minGlyphBoundingBoxY, bounds.y()); | 
|  | } | 
|  |  | 
|  | template <typename TextIterator> | 
|  | unsigned WidthIterator::advanceInternal(TextIterator& textIterator, GlyphBuffer* glyphBuffer) | 
|  | { | 
|  | 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; | 
|  |  | 
|  | 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 = characterWidth(charData.character, glyphData); | 
|  |  | 
|  | if (m_fallbackFonts && lastFontData != fontData && width) { | 
|  | lastFontData = fontData; | 
|  | cacheFallbackFont(charData.character, fontData, primaryFont); | 
|  | } | 
|  |  | 
|  | if (hasExtraSpacing) | 
|  | width = adjustSpacing(width, charData, *fontData, glyphBuffer); | 
|  |  | 
|  | if (m_accountForGlyphBounds) | 
|  | updateGlyphBounds(glyphData, width, !charData.characterOffset); | 
|  |  | 
|  | if (m_forTextEmphasis && !Character::canReceiveTextEmphasis(charData.character)) | 
|  | glyph = 0; | 
|  |  | 
|  | // Advance past the character we just dealt with. | 
|  | textIterator.advance(charData.clusterLength); | 
|  | m_runWidthSoFar += width; | 
|  |  | 
|  | if (glyphBuffer) | 
|  | glyphBuffer->add(glyph, fontData, width); | 
|  | } | 
|  |  | 
|  | unsigned consumedCharacters = textIterator.currentCharacter() - m_currentCharacter; | 
|  | m_currentCharacter = textIterator.currentCharacter(); | 
|  |  | 
|  | return consumedCharacters; | 
|  | } | 
|  |  | 
|  | unsigned WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer) | 
|  | { | 
|  | int length = m_run.length(); | 
|  |  | 
|  | if (offset > length) | 
|  | offset = length; | 
|  |  | 
|  | if (m_currentCharacter >= static_cast<unsigned>(offset)) | 
|  | return 0; | 
|  |  | 
|  | if (m_run.is8Bit()) { | 
|  | Latin1TextIterator textIterator(m_run.data8(m_currentCharacter), m_currentCharacter, offset, length); | 
|  | return advanceInternal(textIterator, glyphBuffer); | 
|  | } | 
|  |  | 
|  | SurrogatePairAwareTextIterator textIterator(m_run.data16(m_currentCharacter), m_currentCharacter, offset, length); | 
|  | return advanceInternal(textIterator, glyphBuffer); | 
|  | } | 
|  |  | 
|  | bool WidthIterator::advanceOneCharacter(float& width) | 
|  | { | 
|  | float initialWidth = m_runWidthSoFar; | 
|  |  | 
|  | if (!advance(m_currentCharacter + 1)) | 
|  | return false; | 
|  |  | 
|  | width = m_runWidthSoFar - initialWidth; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | } // namespace blink |