2010-11-29 Jan Erik Hanssen <jhanssen@sencha.com>
[WebKit-https.git] / WebCore / platform / graphics / qt / FontQt.cpp
1 /*
2     Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
3     Copyright (C) 2008, 2010 Holger Hans Peter Freyther
4     Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
5
6     This library is free software; you can redistribute it and/or
7     modify it under the terms of the GNU Library General Public
8     License as published by the Free Software Foundation; either
9     version 2 of the License, or (at your option) any later version.
10
11     This library is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14     Library General Public License for more details.
15
16     You should have received a copy of the GNU Library General Public License
17     along with this library; see the file COPYING.LIB.  If not, write to
18     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19     Boston, MA 02110-1301, USA.
20 */
21
22 #include "config.h"
23 #include "Font.h"
24
25 #include "AffineTransform.h"
26 #include "ContextShadow.h"
27 #include "FontDescription.h"
28 #include "FontFallbackList.h"
29 #include "FontSelector.h"
30 #include "Gradient.h"
31 #include "GraphicsContext.h"
32 #include "Pattern.h"
33
34 #include <QBrush>
35 #include <QFontInfo>
36 #include <QFontMetrics>
37 #include <QPainter>
38 #include <QPainterPath>
39 #include <QPen>
40 #include <QTextLayout>
41 #include <qalgorithms.h>
42 #include <qdebug.h>
43
44 #include <limits.h>
45
46 namespace WebCore {
47
48 static const QString fromRawDataWithoutRef(const String& string, int start = 0, int len = -1)
49 {
50     if (len < 0)
51         len = string.length() - start;
52     Q_ASSERT(start + len <= string.length());
53
54     // We don't detach. This assumes the WebCore string data will stay valid for the
55     // lifetime of the QString we pass back, since we don't ref the WebCore string.
56     return QString::fromRawData(reinterpret_cast<const QChar*>(string.characters() + start), len);
57 }
58
59 static QTextLine setupLayout(QTextLayout* layout, const TextRun& style)
60 {
61     int flags = style.rtl() ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
62     if (style.padding())
63         flags |= Qt::TextJustificationForced;
64     layout->setFlags(flags);
65     layout->beginLayout();
66     QTextLine line = layout->createLine();
67     line.setLineWidth(INT_MAX/256);
68     if (style.padding())
69         line.setLineWidth(line.naturalTextWidth() + style.padding());
70     layout->endLayout();
71     return line;
72 }
73
74 static void drawTextCommon(GraphicsContext* ctx, const TextRun& run, const FloatPoint& point, int from, int to, const QFont& font, bool isComplexText)
75 {
76     if (to < 0)
77         to = run.length();
78
79     QPainter *p = ctx->platformContext();
80
81     QPen textFillPen;
82     if (ctx->textDrawingMode() & cTextFill) {
83         if (ctx->fillGradient()) {
84             QBrush brush(*ctx->fillGradient()->platformGradient());
85             brush.setTransform(ctx->fillGradient()->gradientSpaceTransform());
86             textFillPen = QPen(brush, 0);
87         } else if (ctx->fillPattern()) {
88             AffineTransform affine;
89             textFillPen = QPen(QBrush(ctx->fillPattern()->createPlatformPattern(affine)), 0);
90         } else
91             textFillPen = QPen(QColor(ctx->fillColor()));
92     }
93
94     QPen textStrokePen;
95     if (ctx->textDrawingMode() & cTextStroke) {
96         if (ctx->strokeGradient()) {
97             QBrush brush(*ctx->strokeGradient()->platformGradient());
98             brush.setTransform(ctx->strokeGradient()->gradientSpaceTransform());
99             textStrokePen = QPen(brush, ctx->strokeThickness());
100         } else if (ctx->strokePattern()) {
101             AffineTransform affine;
102             QBrush brush(ctx->strokePattern()->createPlatformPattern(affine));
103             textStrokePen = QPen(brush, ctx->strokeThickness());
104         } else
105             textStrokePen = QPen(QColor(ctx->strokeColor()), ctx->strokeThickness());
106     }
107
108     String sanitized = Font::normalizeSpaces(String(run.characters(), run.length()));
109     QString string = fromRawDataWithoutRef(sanitized);
110     QPointF pt(point.x(), point.y());
111
112     if (from > 0 || to < run.length()) {
113         if (isComplexText) {
114             QTextLayout layout(string, font);
115             QTextLine line = setupLayout(&layout, run);
116             float x1 = line.cursorToX(from);
117             float x2 = line.cursorToX(to);
118             if (x2 < x1)
119                 qSwap(x1, x2);
120
121             QFontMetrics fm(font);
122             int ascent = fm.ascent();
123             QRectF boundingRect(point.x() + x1, point.y() - ascent, x2 - x1, fm.height());
124             QRectF clip = boundingRect;
125
126             ContextShadow* ctxShadow = ctx->contextShadow();
127
128             if (ctxShadow->m_type != ContextShadow::NoShadow) {
129                 qreal dx1 = 0, dx2 = 0, dy1 = 0, dy2 = 0;
130                 if (ctxShadow->offset().x() > 0)
131                     dx2 = ctxShadow->offset().x();
132                 else
133                     dx1 = -ctxShadow->offset().x();
134                 if (ctxShadow->offset().y() > 0)
135                     dy2 = ctxShadow->offset().y();
136                 else
137                     dy1 = -ctxShadow->offset().y();
138                 // expand the clip rect to include the text shadow as well
139                 clip.adjust(dx1, dx2, dy1, dy2);
140                 clip.adjust(-ctxShadow->m_blurDistance, -ctxShadow->m_blurDistance, ctxShadow->m_blurDistance, ctxShadow->m_blurDistance);
141             }
142             p->save();
143             p->setClipRect(clip.toRect(), Qt::IntersectClip);
144             pt.setY(pt.y() - ascent);
145
146             if (ctxShadow->m_type != ContextShadow::NoShadow) {
147                 ContextShadow* ctxShadow = ctx->contextShadow();
148                 if (ctxShadow->m_type != ContextShadow::BlurShadow) {
149                     p->save();
150                     p->setPen(ctxShadow->m_color);
151                     p->translate(ctxShadow->offset());
152                     line.draw(p, pt);
153                     p->restore();
154                 } else {
155                     QPainter* shadowPainter = ctxShadow->beginShadowLayer(p, boundingRect);
156                     if (shadowPainter) {
157                         // Since it will be blurred anyway, we don't care about render hints.
158                         shadowPainter->setFont(p->font());
159                         shadowPainter->setPen(ctxShadow->m_color);
160                         line.draw(shadowPainter, pt);
161                         ctxShadow->endShadowLayer(p);
162                     }
163                 }
164             }
165             p->setPen(textFillPen);
166             line.draw(p, pt);
167             p->restore();
168             return;
169         }
170 #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
171         int skipWidth = QFontMetrics(font).width(string, from, Qt::TextBypassShaping);
172         pt.setX(pt.x() + skipWidth);
173         string = fromRawDataWithoutRef(sanitized, from, to - from);
174 #endif
175     }
176
177     p->setFont(font);
178
179     int flags = run.rtl() ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
180 #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
181     // See QWebPagePrivate::QWebPagePrivate() where the default path is set to Complex for Qt 4.6 and earlier.
182     if (!isComplexText && !(ctx->textDrawingMode() & cTextStroke))
183         flags |= Qt::TextBypassShaping;
184 #endif
185
186     QPainterPath textStrokePath;
187     if (ctx->textDrawingMode() & cTextStroke)
188         textStrokePath.addText(pt, font, string);
189
190     ContextShadow* ctxShadow = ctx->contextShadow();
191     if (ctxShadow->m_type != ContextShadow::NoShadow) {
192         if (ctx->textDrawingMode() & cTextFill) {
193             if (ctxShadow->m_type != ContextShadow::BlurShadow) {
194                 p->save();
195                 p->setPen(ctxShadow->m_color);
196                 p->translate(ctxShadow->offset());
197                 p->drawText(pt, string, flags, run.padding());
198                 p->restore();
199             } else {
200                 QFontMetrics fm(font);
201 #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
202                 QRectF boundingRect(pt.x(), point.y() - fm.ascent(), fm.width(string, -1, flags), fm.height());
203 #else
204                 QRectF boundingRect(pt.x(), point.y() - fm.ascent(), fm.width(string), fm.height());
205 #endif
206                 QPainter* shadowPainter = ctxShadow->beginShadowLayer(p, boundingRect);
207                 if (shadowPainter) {
208                     // Since it will be blurred anyway, we don't care about render hints.
209                     shadowPainter->setFont(p->font());
210                     shadowPainter->setPen(ctxShadow->m_color);
211                     shadowPainter->drawText(pt, string, flags, run.padding());
212                     ctxShadow->endShadowLayer(p);
213                 }
214             }
215         } else if (ctx->textDrawingMode() & cTextStroke) {
216             if (ctxShadow->m_type != ContextShadow::BlurShadow) {
217                 p->translate(ctxShadow->offset());
218                 p->strokePath(textStrokePath, QPen(ctxShadow->m_color));
219                 p->translate(-ctxShadow->offset());
220             } else {
221                 QFontMetrics fm(font);
222 #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
223                 QRectF boundingRect(pt.x(), point.y() - fm.ascent(), fm.width(string, -1, flags), fm.height());
224 #else
225                 QRectF boundingRect(pt.x(), point.y() - fm.ascent(), fm.width(string), fm.height());
226 #endif
227                 QPainter* shadowPainter = ctxShadow->beginShadowLayer(p, boundingRect);
228                 if (shadowPainter) {
229                     // Since it will be blurred anyway, we don't care about render hints.
230                     shadowPainter->setFont(p->font());
231                     shadowPainter->strokePath(textStrokePath, QPen(ctxShadow->m_color));
232                     ctxShadow->endShadowLayer(p);
233                 }
234             }
235         }
236     }
237
238     if (ctx->textDrawingMode() & cTextStroke)
239         p->strokePath(textStrokePath, textStrokePen);
240
241     if (ctx->textDrawingMode() & cTextFill) {
242         QPen previousPen = p->pen();
243         p->setPen(textFillPen);
244         p->drawText(pt, string, flags, run.padding());
245         p->setPen(previousPen);
246     }
247 }
248
249 void Font::drawSimpleText(GraphicsContext* ctx, const TextRun& run, const FloatPoint& point, int from, int to) const
250 {
251 #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
252     drawTextCommon(ctx, run, point, from, to, font(), /* isComplexText = */false);
253 #else
254     Q_ASSERT(false);
255 #endif
256 }
257
258 void Font::drawComplexText(GraphicsContext* ctx, const TextRun& run, const FloatPoint& point, int from, int to) const
259 {
260     drawTextCommon(ctx, run, point, from, to, font(), /* isComplexText = */true);
261 }
262
263 float Font::floatWidthForSimpleText(const TextRun& run, GlyphBuffer* glyphBuffer, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
264 {
265 #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
266     if (!run.length())
267         return 0;
268
269     String sanitized = Font::normalizeSpaces(String(run.characters(), run.length()));
270     QString string = fromRawDataWithoutRef(sanitized);
271
272     int w = QFontMetrics(font()).width(string, -1, Qt::TextBypassShaping);
273
274     // WebKit expects us to ignore word spacing on the first character (as opposed to what Qt does)
275     if (treatAsSpace(run[0]))
276         w -= m_wordSpacing;
277
278     return w + run.padding();
279 #else
280     Q_ASSERT(false);
281     return 0.0f;
282 #endif
283 }
284
285 float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>*, GlyphOverflow*) const
286 {
287     if (!run.length())
288         return 0;
289
290     if (run.length() == 1 && treatAsSpace(run[0]))
291         return QFontMetrics(font()).width(space) + run.padding();
292
293     String sanitized = Font::normalizeSpaces(String(run.characters(), run.length()));
294     QString string = fromRawDataWithoutRef(sanitized);
295
296     int w = QFontMetrics(font()).width(string);
297     // WebKit expects us to ignore word spacing on the first character (as opposed to what Qt does)
298     if (treatAsSpace(run[0]))
299         w -= m_wordSpacing;
300
301     return w + run.padding();
302 }
303
304 int Font::offsetForPositionForSimpleText(const TextRun& run, float position, bool includePartialGlyphs) const
305 {
306 #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
307     String sanitized = Font::normalizeSpaces(String(run.characters(), run.length()));
308     QString string = fromRawDataWithoutRef(sanitized);
309
310     QFontMetrics fm(font());
311     float delta = position;
312     int curPos = 0;
313     do {
314         float charWidth = fm.width(string[curPos]);
315         delta -= charWidth;
316         if (includePartialGlyphs) {
317             if (delta + charWidth / 2 <= 0)
318                 break;
319         } else {
320             if (delta + charWidth <= 0)
321                 break;
322         }
323     } while (++curPos < string.size());
324
325     return curPos;
326 #else
327     Q_ASSERT(false);
328     return 0;
329 #endif
330 }
331
332 int Font::offsetForPositionForComplexText(const TextRun& run, float position, bool) const
333 {
334     String sanitized = Font::normalizeSpaces(String(run.characters(), run.length()));
335     QString string = fromRawDataWithoutRef(sanitized);
336
337     QTextLayout layout(string, font());
338     QTextLine line = setupLayout(&layout, run);
339     return line.xToCursor(position);
340 }
341
342 FloatRect Font::selectionRectForSimpleText(const TextRun& run, const FloatPoint& pt, int h, int from, int to) const
343 {
344 #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
345     String sanitized = Font::normalizeSpaces(String(run.characters(), run.length()));
346     QString wholeText = fromRawDataWithoutRef(sanitized);
347     QString selectedText = fromRawDataWithoutRef(sanitized, from, qMin(to - from, wholeText.length() - from));
348
349     int startX = QFontMetrics(font()).width(wholeText, from, Qt::TextBypassShaping);
350     int width = QFontMetrics(font()).width(selectedText, -1, Qt::TextBypassShaping);
351
352     return FloatRect(pt.x() + startX, pt.y(), width, h);
353 #else
354     Q_ASSERT(false);
355     return FloatRect();
356 #endif
357 }
358
359 FloatRect Font::selectionRectForComplexText(const TextRun& run, const FloatPoint& pt, int h, int from, int to) const
360 {
361     String sanitized = Font::normalizeSpaces(String(run.characters(), run.length()));
362     QString string = fromRawDataWithoutRef(sanitized);
363
364     QTextLayout layout(string, font());
365     QTextLine line = setupLayout(&layout, run);
366
367     float x1 = line.cursorToX(from);
368     float x2 = line.cursorToX(to);
369     if (x2 < x1)
370         qSwap(x1, x2);
371
372     return FloatRect(pt.x() + x1, pt.y(), x2 - x1, h);
373 }
374
375 bool Font::canReturnFallbackFontsForComplexText()
376 {
377     return false;
378 }
379
380 QFont Font::font() const
381 {
382     QFont f = primaryFont()->getQtFont();
383     if (m_letterSpacing != 0)
384         f.setLetterSpacing(QFont::AbsoluteSpacing, m_letterSpacing);
385     if (m_wordSpacing != 0)
386         f.setWordSpacing(m_wordSpacing);
387     return f;
388 }
389
390 }
391