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