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 "core/platform/graphics/WidthIterator.h"
24
25#include "core/platform/graphics/Font.h"
26#include "core/platform/graphics/GlyphBuffer.h"
27#include "core/platform/graphics/Latin1TextIterator.h"
28#include "core/platform/graphics/SimpleFontData.h"
29#include "core/platform/graphics/SurrogatePairAwareTextIterator.h"
30#include "wtf/MathExtras.h"
31
32using namespace WTF;
33using namespace Unicode;
34using namespace std;
35
36namespace WebCore {
37
38WidthIterator::WidthIterator(const Font* font, const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts, bool accountForGlyphBounds, bool forTextEmphasis)
39    : m_font(font)
40    , m_run(run)
41    , m_currentCharacter(0)
42    , m_runWidthSoFar(0)
43    , m_isAfterExpansion(!run.allowsLeadingExpansion())
44    , m_finalRoundingWidth(0)
45    , m_typesettingFeatures(font->typesettingFeatures())
46    , m_fallbackFonts(fallbackFonts)
47    , m_accountForGlyphBounds(accountForGlyphBounds)
48    , m_maxGlyphBoundingBoxY(numeric_limits<float>::min())
49    , m_minGlyphBoundingBoxY(numeric_limits<float>::max())
50    , m_firstGlyphOverflow(0)
51    , m_lastGlyphOverflow(0)
52    , m_forTextEmphasis(forTextEmphasis)
53{
54    // If the padding is non-zero, count the number of spaces in the run
55    // and divide that by the padding for per space addition.
56    m_expansion = m_run.expansion();
57    if (!m_expansion)
58        m_expansionPerOpportunity = 0;
59    else {
60        bool isAfterExpansion = m_isAfterExpansion;
61        unsigned expansionOpportunityCount = m_run.is8Bit() ? Font::expansionOpportunityCount(m_run.characters8(), m_run.length(), m_run.ltr() ? LTR : RTL, isAfterExpansion) : Font::expansionOpportunityCount(m_run.characters16(), m_run.length(), m_run.ltr() ? LTR : RTL, isAfterExpansion);
62        if (isAfterExpansion && !m_run.allowsTrailingExpansion())
63            expansionOpportunityCount--;
64
65        if (!expansionOpportunityCount)
66            m_expansionPerOpportunity = 0;
67        else
68            m_expansionPerOpportunity = m_expansion / expansionOpportunityCount;
69    }
70}
71
72GlyphData WidthIterator::glyphDataForCharacter(UChar32 character, bool mirror, int currentCharacter, unsigned& advanceLength)
73{
74    ASSERT(m_font);
75
76#if ENABLE(SVG_FONTS)
77    if (TextRun::RenderingContext* renderingContext = m_run.renderingContext())
78        return renderingContext->glyphDataForCharacter(*m_font, m_run, *this, character, mirror, currentCharacter, advanceLength);
79#else
80    UNUSED_PARAM(currentCharacter);
81    UNUSED_PARAM(advanceLength);
82#endif
83
84    return m_font->glyphDataForCharacter(character, mirror);
85}
86
87struct OriginalAdvancesForCharacterTreatedAsSpace {
88public:
89    OriginalAdvancesForCharacterTreatedAsSpace(bool isSpace, float advanceBefore, float advanceAt)
90        : characterIsSpace(isSpace)
91        , advanceBeforeCharacter(advanceBefore)
92        , advanceAtCharacter(advanceAt)
93    {
94    }
95
96    bool characterIsSpace;
97    float advanceBeforeCharacter;
98    float advanceAtCharacter;
99};
100
101typedef Vector<pair<int, OriginalAdvancesForCharacterTreatedAsSpace>, 64> CharactersTreatedAsSpace;
102
103static inline float applyFontTransforms(GlyphBuffer* glyphBuffer, bool ltr, int& lastGlyphCount, const SimpleFontData* fontData, TypesettingFeatures typesettingFeatures, CharactersTreatedAsSpace& charactersTreatedAsSpace)
104{
105    ASSERT(typesettingFeatures & (Kerning | Ligatures));
106
107    if (!glyphBuffer)
108        return 0;
109
110    int glyphBufferSize = glyphBuffer->size();
111    if (glyphBuffer->size() <= lastGlyphCount + 1)
112        return 0;
113
114    GlyphBufferAdvance* advances = glyphBuffer->advances(0);
115    float widthDifference = 0;
116    for (int i = lastGlyphCount; i < glyphBufferSize; ++i)
117        widthDifference -= advances[i].width();
118
119    if (!ltr)
120        glyphBuffer->reverse(lastGlyphCount, glyphBufferSize - lastGlyphCount);
121
122    fontData->applyTransforms(glyphBuffer->glyphs(lastGlyphCount), advances + lastGlyphCount, glyphBufferSize - lastGlyphCount, typesettingFeatures);
123
124    if (!ltr)
125        glyphBuffer->reverse(lastGlyphCount, glyphBufferSize - lastGlyphCount);
126
127    for (size_t i = 0; i < charactersTreatedAsSpace.size(); ++i) {
128        int spaceOffset = charactersTreatedAsSpace[i].first;
129        const OriginalAdvancesForCharacterTreatedAsSpace& originalAdvances = charactersTreatedAsSpace[i].second;
130        if (spaceOffset && !originalAdvances.characterIsSpace)
131            glyphBuffer->advances(spaceOffset - 1)->setWidth(originalAdvances.advanceBeforeCharacter);
132        glyphBuffer->advances(spaceOffset)->setWidth(originalAdvances.advanceAtCharacter);
133    }
134    charactersTreatedAsSpace.clear();
135
136    for (int i = lastGlyphCount; i < glyphBufferSize; ++i)
137        widthDifference += advances[i].width();
138
139    lastGlyphCount = glyphBufferSize;
140    return widthDifference;
141}
142
143template <typename TextIterator>
144inline unsigned WidthIterator::advanceInternal(TextIterator& textIterator, GlyphBuffer* glyphBuffer)
145{
146    bool rtl = m_run.rtl();
147    bool hasExtraSpacing = (m_font->letterSpacing() || m_font->wordSpacing() || m_expansion) && !m_run.spacingDisabled();
148
149    float widthSinceLastRounding = m_runWidthSoFar;
150    m_runWidthSoFar = floorf(m_runWidthSoFar);
151    widthSinceLastRounding -= m_runWidthSoFar;
152
153    float lastRoundingWidth = m_finalRoundingWidth;
154    FloatRect bounds;
155
156    const SimpleFontData* primaryFont = m_font->primaryFont();
157    const SimpleFontData* lastFontData = primaryFont;
158    int lastGlyphCount = glyphBuffer ? glyphBuffer->size() : 0;
159
160    UChar32 character = 0;
161    unsigned clusterLength = 0;
162    CharactersTreatedAsSpace charactersTreatedAsSpace;
163    while (textIterator.consume(character, clusterLength)) {
164        unsigned advanceLength = clusterLength;
165        const GlyphData& glyphData = glyphDataForCharacter(character, rtl, textIterator.currentCharacter(), advanceLength);
166        Glyph glyph = glyphData.glyph;
167        const SimpleFontData* fontData = glyphData.fontData;
168
169        ASSERT(fontData);
170
171        // Now that we have a glyph and font data, get its width.
172        float width;
173        if (character == '\t' && m_run.allowTabs())
174            width = m_font->tabWidth(*fontData, m_run.tabSize(), m_run.xPos() + m_runWidthSoFar + widthSinceLastRounding);
175        else {
176            width = fontData->widthForGlyph(glyph);
177
178            // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text.
179            width *= m_run.horizontalGlyphStretch();
180
181            // We special case spaces in two ways when applying word rounding.
182            // First, we round spaces to an adjusted width in all fonts.
183            // Second, in fixed-pitch fonts we ensure that all characters that
184            // match the width of the space character have the same width as the space character.
185            if (m_run.applyWordRounding() && width == fontData->spaceWidth() && (fontData->pitch() == FixedPitch || glyph == fontData->spaceGlyph()))
186                width = fontData->adjustedSpaceWidth();
187        }
188
189        if (fontData != lastFontData && width) {
190            if (shouldApplyFontTransforms())
191                m_runWidthSoFar += applyFontTransforms(glyphBuffer, m_run.ltr(), lastGlyphCount, lastFontData, m_typesettingFeatures, charactersTreatedAsSpace);
192
193            lastFontData = fontData;
194            if (m_fallbackFonts && fontData != primaryFont) {
195                // FIXME: This does a little extra work that could be avoided if
196                // glyphDataForCharacter() returned whether it chose to use a small caps font.
197                if (!m_font->isSmallCaps() || character == toUpper(character))
198                    m_fallbackFonts->add(fontData);
199                else {
200                    const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(toUpper(character), rtl);
201                    if (uppercaseGlyphData.fontData != primaryFont)
202                        m_fallbackFonts->add(uppercaseGlyphData.fontData);
203                }
204            }
205        }
206
207        if (hasExtraSpacing) {
208            // Account for letter-spacing.
209            if (width && m_font->letterSpacing())
210                width += m_font->letterSpacing();
211
212            static bool expandAroundIdeographs = Font::canExpandAroundIdeographsInComplexText();
213            bool treatAsSpace = Font::treatAsSpace(character);
214            if (treatAsSpace || (expandAroundIdeographs && Font::isCJKIdeographOrSymbol(character))) {
215                // Distribute the run's total expansion evenly over all expansion opportunities in the run.
216                if (m_expansion) {
217                    float previousExpansion = m_expansion;
218                    if (!treatAsSpace && !m_isAfterExpansion) {
219                        // Take the expansion opportunity before this ideograph.
220                        m_expansion -= m_expansionPerOpportunity;
221                        float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
222                        m_runWidthSoFar += expansionAtThisOpportunity;
223                        if (glyphBuffer) {
224                            if (glyphBuffer->isEmpty()) {
225                                if (m_forTextEmphasis)
226                                    glyphBuffer->add(fontData->zeroWidthSpaceGlyph(), fontData, m_expansionPerOpportunity);
227                                else
228                                    glyphBuffer->add(fontData->spaceGlyph(), fontData, expansionAtThisOpportunity);
229                            } else
230                                glyphBuffer->expandLastAdvance(expansionAtThisOpportunity);
231                        }
232                        previousExpansion = m_expansion;
233                    }
234                    if (m_run.allowsTrailingExpansion() || (m_run.ltr() && textIterator.currentCharacter() + advanceLength < static_cast<size_t>(m_run.length()))
235                        || (m_run.rtl() && textIterator.currentCharacter())) {
236                        m_expansion -= m_expansionPerOpportunity;
237                        width += !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
238                        m_isAfterExpansion = true;
239                    }
240                } else
241                    m_isAfterExpansion = false;
242
243                // Account for word spacing.
244                // We apply additional space between "words" by adding width to the space character.
245                if (treatAsSpace && (character != '\t' || !m_run.allowTabs()) && (textIterator.currentCharacter() || character == noBreakSpace) && m_font->wordSpacing())
246                    width += m_font->wordSpacing();
247            } else
248                m_isAfterExpansion = false;
249        }
250
251        if (shouldApplyFontTransforms() && glyphBuffer && Font::treatAsSpace(character))
252            charactersTreatedAsSpace.append(make_pair(glyphBuffer->size(),
253                OriginalAdvancesForCharacterTreatedAsSpace(character == ' ', glyphBuffer->size() ? glyphBuffer->advanceAt(glyphBuffer->size() - 1) : 0, width)));
254
255        if (m_accountForGlyphBounds) {
256            bounds = fontData->boundsForGlyph(glyph);
257            if (!textIterator.currentCharacter())
258                m_firstGlyphOverflow = max<float>(0, -bounds.x());
259        }
260
261        if (m_forTextEmphasis && !Font::canReceiveTextEmphasis(character))
262            glyph = 0;
263
264        // Advance past the character we just dealt with.
265        textIterator.advance(advanceLength);
266
267        float oldWidth = width;
268
269        // Force characters that are used to determine word boundaries for the rounding hack
270        // to be integer width, so following words will start on an integer boundary.
271        if (m_run.applyWordRounding() && Font::isRoundingHackCharacter(character)) {
272            width = ceilf(width);
273
274            // Since widthSinceLastRounding can lose precision if we include measurements for
275            // preceding whitespace, we bypass it here.
276            m_runWidthSoFar += width;
277
278            // Since this is a rounding hack character, we should have reset this sum on the previous
279            // iteration.
280            ASSERT(!widthSinceLastRounding);
281        } else {
282            // Check to see if the next character is a "rounding hack character", if so, adjust
283            // width so that the total run width will be on an integer boundary.
284            if ((m_run.applyWordRounding() && textIterator.currentCharacter() < m_run.length() && Font::isRoundingHackCharacter(*(textIterator.characters())))
285                || (m_run.applyRunRounding() && textIterator.currentCharacter() >= m_run.length())) {
286                float totalWidth = widthSinceLastRounding + width;
287                widthSinceLastRounding = ceilf(totalWidth);
288                width += widthSinceLastRounding - totalWidth;
289                m_runWidthSoFar += widthSinceLastRounding;
290                widthSinceLastRounding = 0;
291            } else
292                widthSinceLastRounding += width;
293        }
294
295        if (glyphBuffer)
296            glyphBuffer->add(glyph, fontData, (rtl ? oldWidth + lastRoundingWidth : width));
297
298        lastRoundingWidth = width - oldWidth;
299
300        if (m_accountForGlyphBounds) {
301            m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, bounds.maxY());
302            m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, bounds.y());
303            m_lastGlyphOverflow = max<float>(0, bounds.maxX() - width);
304        }
305    }
306
307    if (shouldApplyFontTransforms())
308        m_runWidthSoFar += applyFontTransforms(glyphBuffer, m_run.ltr(), lastGlyphCount, lastFontData, m_typesettingFeatures, charactersTreatedAsSpace);
309
310    unsigned consumedCharacters = textIterator.currentCharacter() - m_currentCharacter;
311    m_currentCharacter = textIterator.currentCharacter();
312    m_runWidthSoFar += widthSinceLastRounding;
313    m_finalRoundingWidth = lastRoundingWidth;
314    return consumedCharacters;
315}
316
317unsigned WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer)
318{
319    int length = m_run.length();
320
321    if (offset > length)
322        offset = length;
323
324    if (m_currentCharacter >= static_cast<unsigned>(offset))
325        return 0;
326
327    if (m_run.is8Bit()) {
328        Latin1TextIterator textIterator(m_run.data8(m_currentCharacter), m_currentCharacter, offset, length);
329        return advanceInternal(textIterator, glyphBuffer);
330    }
331
332    SurrogatePairAwareTextIterator textIterator(m_run.data16(m_currentCharacter), m_currentCharacter, offset, length);
333    return advanceInternal(textIterator, glyphBuffer);
334}
335
336bool WidthIterator::advanceOneCharacter(float& width, GlyphBuffer& glyphBuffer)
337{
338    int oldSize = glyphBuffer.size();
339    advance(m_currentCharacter + 1, &glyphBuffer);
340    float w = 0;
341    for (int i = oldSize; i < glyphBuffer.size(); ++i)
342        w += glyphBuffer.advanceAt(i);
343    width = w;
344    return glyphBuffer.size() > oldSize;
345}
346
347}
348