Add an option to enable legacy rounding hacks
[WebKit.git] / Source / WebCore / platform / graphics / WidthIterator.cpp
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 "WidthIterator.h"
24
25 #include "Font.h"
26 #include "GlyphBuffer.h"
27 #include "SimpleFontData.h"
28 #include "SurrogatePairAwareTextIterator.h"
29 #include "TextRun.h"
30 #include <wtf/MathExtras.h>
31
32 using namespace WTF;
33 using namespace Unicode;
34 using namespace std;
35
36 namespace WebCore {
37
38 WidthIterator::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_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 = Font::expansionOpportunityCount(m_run.characters(), 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
71 GlyphData 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 #else
79     UNUSED_PARAM(currentCharacter);
80     UNUSED_PARAM(advanceLength);
81 #endif
82
83     return m_font->glyphDataForCharacter(character, mirror);
84 }
85
86 unsigned WidthIterator::advance(int offset, GlyphBuffer* glyphBuffer)
87 {
88     if (offset > m_run.length())
89         offset = m_run.length();
90
91     if (int(m_currentCharacter) >= offset)
92         return 0;
93
94     bool rtl = m_run.rtl();
95     bool hasExtraSpacing = (m_font->letterSpacing() || m_font->wordSpacing() || m_expansion) && !m_run.spacingDisabled();
96
97     float widthSinceLastRounding = m_runWidthSoFar;
98     m_runWidthSoFar = floorf(m_runWidthSoFar);
99     widthSinceLastRounding -= m_runWidthSoFar;
100
101     float lastRoundingWidth = m_finalRoundingWidth;
102     FloatRect bounds;
103
104     const SimpleFontData* primaryFont = m_font->primaryFont();
105     const SimpleFontData* lastFontData = primaryFont;
106
107     UChar32 character = 0;
108     unsigned clusterLength = 0;
109     SurrogatePairAwareTextIterator textIterator(m_run.data(m_currentCharacter), m_currentCharacter, offset, m_run.length());
110     while (textIterator.consume(character, clusterLength)) {
111         unsigned advanceLength = clusterLength;
112         const GlyphData& glyphData = glyphDataForCharacter(character, rtl, textIterator.currentCharacter(), advanceLength);
113         Glyph glyph = glyphData.glyph;
114         const SimpleFontData* fontData = glyphData.fontData;
115
116         ASSERT(fontData);
117
118         // Now that we have a glyph and font data, get its width.
119         float width;
120         if (character == '\t' && m_run.allowTabs()) {
121             float tabWidth = m_font->tabWidth(*fontData);
122             width = tabWidth - fmodf(m_run.xPos() + m_runWidthSoFar + widthSinceLastRounding, tabWidth);
123         } else {
124             width = fontData->widthForGlyph(glyph);
125
126 #if ENABLE(SVG)
127             // SVG uses horizontalGlyphStretch(), when textLength is used to stretch/squeeze text.
128             width *= m_run.horizontalGlyphStretch();
129 #endif
130
131             // We special case spaces in two ways when applying word rounding.
132             // First, we round spaces to an adjusted width in all fonts.
133             // Second, in fixed-pitch fonts we ensure that all characters that
134             // match the width of the space character have the same width as the space character.
135             if (m_run.applyWordRounding() && width == fontData->spaceWidth() && (fontData->pitch() == FixedPitch || glyph == fontData->spaceGlyph()))
136                 width = fontData->adjustedSpaceWidth();
137         }
138
139         if (fontData != lastFontData && width) {
140             lastFontData = fontData;
141             if (m_fallbackFonts && fontData != primaryFont) {
142                 // FIXME: This does a little extra work that could be avoided if
143                 // glyphDataForCharacter() returned whether it chose to use a small caps font.
144                 if (!m_font->isSmallCaps() || character == toUpper(character))
145                     m_fallbackFonts->add(fontData);
146                 else {
147                     const GlyphData& uppercaseGlyphData = m_font->glyphDataForCharacter(toUpper(character), rtl);
148                     if (uppercaseGlyphData.fontData != primaryFont)
149                         m_fallbackFonts->add(uppercaseGlyphData.fontData);
150                 }
151             }
152         }
153
154         if (hasExtraSpacing) {
155             // Account for letter-spacing.
156             if (width && m_font->letterSpacing())
157                 width += m_font->letterSpacing();
158
159             static bool expandAroundIdeographs = Font::canExpandAroundIdeographsInComplexText();
160             bool treatAsSpace = Font::treatAsSpace(character);
161             if (treatAsSpace || (expandAroundIdeographs && Font::isCJKIdeographOrSymbol(character))) {
162                 // Distribute the run's total expansion evenly over all expansion opportunities in the run.
163                 if (m_expansion) {
164                     if (!treatAsSpace && !m_isAfterExpansion) {
165                         // Take the expansion opportunity before this ideograph.
166                         m_expansion -= m_expansionPerOpportunity;
167                         m_runWidthSoFar += m_expansionPerOpportunity;
168                         if (glyphBuffer) {
169                             if (glyphBuffer->isEmpty())
170                                 glyphBuffer->add(fontData->spaceGlyph(), fontData, m_expansionPerOpportunity);
171                             else
172                                 glyphBuffer->expandLastAdvance(m_expansionPerOpportunity);
173                         }
174                     }
175                     if (m_run.allowsTrailingExpansion() || (m_run.ltr() && textIterator.currentCharacter() + advanceLength < static_cast<size_t>(m_run.length()))
176                         || (m_run.rtl() && textIterator.currentCharacter())) {
177                         m_expansion -= m_expansionPerOpportunity;
178                         width += m_expansionPerOpportunity;
179                         m_isAfterExpansion = true;
180                     }
181                 } else
182                     m_isAfterExpansion = false;
183
184                 // Account for word spacing.
185                 // We apply additional space between "words" by adding width to the space character.
186                 if (treatAsSpace && textIterator.currentCharacter() && !Font::treatAsSpace(textIterator.characters()[-1]) && m_font->wordSpacing())
187                     width += m_font->wordSpacing();
188             } else
189                 m_isAfterExpansion = false;
190         }
191
192         if (m_accountForGlyphBounds) {
193             bounds = fontData->boundsForGlyph(glyph);
194             if (!textIterator.currentCharacter())
195                 m_firstGlyphOverflow = max<float>(0, -bounds.x());
196         }
197
198         if (m_forTextEmphasis && !Font::canReceiveTextEmphasis(character))
199             glyph = 0;
200
201         // Advance past the character we just dealt with.
202         textIterator.advance(advanceLength);
203
204         float oldWidth = width; 
205
206         // Force characters that are used to determine word boundaries for the rounding hack
207         // to be integer width, so following words will start on an integer boundary.
208         if (m_run.applyWordRounding() && Font::isRoundingHackCharacter(character)) {
209             width = ceilf(width);
210
211             // Since widthSinceLastRounding can lose precision if we include measurements for
212             // preceding whitespace, we bypass it here.
213             m_runWidthSoFar += width;
214
215             // Since this is a rounding hack character, we should have reset this sum on the previous
216             // iteration.
217             ASSERT(!widthSinceLastRounding);
218         } else {
219             // Check to see if the next character is a "rounding hack character", if so, adjust
220             // width so that the total run width will be on an integer boundary.
221             if ((m_run.applyWordRounding() && textIterator.currentCharacter() < m_run.length() && Font::isRoundingHackCharacter(*(textIterator.characters())))
222                     || (m_run.applyRunRounding() && textIterator.currentCharacter() >= m_run.length())) {
223                 float totalWidth = widthSinceLastRounding + width;
224                 widthSinceLastRounding = ceilf(totalWidth);
225                 width += widthSinceLastRounding - totalWidth;
226                 m_runWidthSoFar += widthSinceLastRounding;
227                 widthSinceLastRounding = 0;
228             } else
229                 widthSinceLastRounding += width;
230         }
231
232         if (glyphBuffer)
233             glyphBuffer->add(glyph, fontData, (rtl ? oldWidth + lastRoundingWidth : width));
234
235         lastRoundingWidth = width - oldWidth;
236
237         if (m_accountForGlyphBounds) {
238             m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, bounds.maxY());
239             m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, bounds.y());
240             m_lastGlyphOverflow = max<float>(0, bounds.maxX() - width);
241         }
242     }
243
244     unsigned consumedCharacters = textIterator.currentCharacter() - m_currentCharacter;
245     m_currentCharacter = textIterator.currentCharacter();
246     m_runWidthSoFar += widthSinceLastRounding;
247     m_finalRoundingWidth = lastRoundingWidth;
248     return consumedCharacters;
249 }
250
251 bool WidthIterator::advanceOneCharacter(float& width, GlyphBuffer* glyphBuffer)
252 {
253     int oldSize = glyphBuffer->size();
254     advance(m_currentCharacter + 1, glyphBuffer);
255     float w = 0;
256     for (int i = oldSize; i < glyphBuffer->size(); ++i)
257         w += glyphBuffer->advanceAt(i);
258     width = w;
259     return glyphBuffer->size() > oldSize;
260 }
261
262 }