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 "FontPlatformData.h"
31 #if HAVE(QRAWFONT)
32 #include "GlyphBuffer.h"
33 #endif
34 #include "Gradient.h"
35 #include "GraphicsContext.h"
36 #include "NotImplemented.h"
37 #include "Pattern.h"
38 #include "TextRun.h"
39 
40 #include <QBrush>
41 #include <QFontInfo>
42 #include <QFontMetrics>
43 #include <QPainter>
44 #include <QPainterPath>
45 #include <QPen>
46 #include <QTextLayout>
47 #include <qalgorithms.h>
48 #include <qdebug.h>
49 
50 #include <limits.h>
51 
52 namespace WebCore {
53 
fromRawDataWithoutRef(const String & string,int start=0,int len=-1)54 static const QString fromRawDataWithoutRef(const String& string, int start = 0, int len = -1)
55 {
56     if (len < 0)
57         len = string.length() - start;
58     Q_ASSERT(start + len <= string.length());
59 
60     // We don't detach. This assumes the WebCore string data will stay valid for the
61     // lifetime of the QString we pass back, since we don't ref the WebCore string.
62     return QString::fromRawData(reinterpret_cast<const QChar*>(string.characters() + start), len);
63 }
64 
setupLayout(QTextLayout * layout,const TextRun & style)65 static QTextLine setupLayout(QTextLayout* layout, const TextRun& style)
66 {
67     int flags = style.rtl() ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
68     if (style.expansion())
69         flags |= Qt::TextJustificationForced;
70     layout->setFlags(flags);
71     layout->beginLayout();
72     QTextLine line = layout->createLine();
73     line.setLineWidth(INT_MAX/256);
74     if (style.expansion())
75         line.setLineWidth(line.naturalTextWidth() + style.expansion());
76     layout->endLayout();
77     return line;
78 }
79 
fillPenForContext(GraphicsContext * ctx)80 static QPen fillPenForContext(GraphicsContext* ctx)
81 {
82     if (ctx->fillGradient()) {
83         QBrush brush(*ctx->fillGradient()->platformGradient());
84         brush.setTransform(ctx->fillGradient()->gradientSpaceTransform());
85         return QPen(brush, 0);
86     }
87 
88     if (ctx->fillPattern()) {
89         AffineTransform affine;
90         return QPen(QBrush(ctx->fillPattern()->createPlatformPattern(affine)), 0);
91     }
92 
93     return QPen(QColor(ctx->fillColor()));
94 }
95 
strokePenForContext(GraphicsContext * ctx)96 static QPen strokePenForContext(GraphicsContext* ctx)
97 {
98     if (ctx->strokeGradient()) {
99         QBrush brush(*ctx->strokeGradient()->platformGradient());
100         brush.setTransform(ctx->strokeGradient()->gradientSpaceTransform());
101         return QPen(brush, ctx->strokeThickness());
102     }
103 
104     if (ctx->strokePattern()) {
105         AffineTransform affine;
106         QBrush brush(ctx->strokePattern()->createPlatformPattern(affine));
107         return QPen(brush, ctx->strokeThickness());
108     }
109 
110     return QPen(QColor(ctx->strokeColor()), ctx->strokeThickness());
111 }
112 
drawTextCommon(GraphicsContext * ctx,const TextRun & run,const FloatPoint & point,int from,int to,const QFont & font,bool isComplexText)113 static void drawTextCommon(GraphicsContext* ctx, const TextRun& run, const FloatPoint& point, int from, int to, const QFont& font, bool isComplexText)
114 {
115     if (to < 0)
116         to = run.length();
117 
118     QPainter *p = ctx->platformContext();
119 
120     QPen textFillPen;
121     if (ctx->textDrawingMode() & TextModeFill)
122         textFillPen = fillPenForContext(ctx);
123 
124     QPen textStrokePen;
125     if (ctx->textDrawingMode() & TextModeStroke)
126         textStrokePen = strokePenForContext(ctx);
127 
128     String sanitized = Font::normalizeSpaces(run.characters(), run.length());
129     QString string = fromRawDataWithoutRef(sanitized);
130     QPointF pt(point.x(), point.y());
131 
132     if (from > 0 || to < run.length()) {
133         if (isComplexText) {
134             QTextLayout layout(string, font);
135             QTextLine line = setupLayout(&layout, run);
136             float x1 = line.cursorToX(from);
137             float x2 = line.cursorToX(to);
138             if (x2 < x1)
139                 qSwap(x1, x2);
140 
141             QFontMetrics fm(font);
142             int ascent = fm.ascent();
143             QRectF boundingRect(point.x() + x1, point.y() - ascent, x2 - x1, fm.height());
144             QRectF clip = boundingRect;
145 
146             ContextShadow* ctxShadow = ctx->contextShadow();
147 
148             if (ctxShadow->m_type != ContextShadow::NoShadow) {
149                 qreal dx1 = 0, dx2 = 0, dy1 = 0, dy2 = 0;
150                 if (ctxShadow->offset().x() > 0)
151                     dx2 = ctxShadow->offset().x();
152                 else
153                     dx1 = -ctxShadow->offset().x();
154                 if (ctxShadow->offset().y() > 0)
155                     dy2 = ctxShadow->offset().y();
156                 else
157                     dy1 = -ctxShadow->offset().y();
158                 // expand the clip rect to include the text shadow as well
159                 clip.adjust(dx1, dx2, dy1, dy2);
160                 clip.adjust(-ctxShadow->m_blurDistance, -ctxShadow->m_blurDistance, ctxShadow->m_blurDistance, ctxShadow->m_blurDistance);
161             }
162             p->save();
163             p->setClipRect(clip.toRect(), Qt::IntersectClip);
164             pt.setY(pt.y() - ascent);
165 
166             if (ctxShadow->m_type != ContextShadow::NoShadow) {
167                 ContextShadow* ctxShadow = ctx->contextShadow();
168                 if (!ctxShadow->mustUseContextShadow(ctx)) {
169                     p->save();
170                     p->setPen(ctxShadow->m_color);
171                     p->translate(ctxShadow->offset());
172                     line.draw(p, pt);
173                     p->restore();
174                 } else {
175                     QPainter* shadowPainter = ctxShadow->beginShadowLayer(ctx, boundingRect);
176                     if (shadowPainter) {
177                         // Since it will be blurred anyway, we don't care about render hints.
178                         shadowPainter->setFont(p->font());
179                         shadowPainter->setPen(ctxShadow->m_color);
180                         line.draw(shadowPainter, pt);
181                         ctxShadow->endShadowLayer(ctx);
182                     }
183                 }
184             }
185             p->setPen(textFillPen);
186             line.draw(p, pt);
187             p->restore();
188             return;
189         }
190         int skipWidth = QFontMetrics(font).width(string, from, Qt::TextBypassShaping);
191         pt.setX(pt.x() + skipWidth);
192         string = fromRawDataWithoutRef(sanitized, from, to - from);
193     }
194 
195     p->setFont(font);
196 
197     int flags = run.rtl() ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
198     if (!isComplexText && !(ctx->textDrawingMode() & TextModeStroke))
199         flags |= Qt::TextBypassShaping;
200 
201     QPainterPath textStrokePath;
202     if (ctx->textDrawingMode() & TextModeStroke)
203         textStrokePath.addText(pt, font, string);
204 
205     ContextShadow* ctxShadow = ctx->contextShadow();
206     if (ctxShadow->m_type != ContextShadow::NoShadow) {
207         if (ctx->textDrawingMode() & TextModeFill) {
208             if (ctxShadow->m_type != ContextShadow::BlurShadow) {
209                 p->save();
210                 p->setPen(ctxShadow->m_color);
211                 p->translate(ctxShadow->offset());
212                 p->drawText(pt, string, flags, run.expansion());
213                 p->restore();
214             } else {
215                 QFontMetrics fm(font);
216                 QRectF boundingRect(pt.x(), point.y() - fm.ascent(), fm.width(string, -1, flags), fm.height());
217                 QPainter* shadowPainter = ctxShadow->beginShadowLayer(ctx, boundingRect);
218                 if (shadowPainter) {
219                     // Since it will be blurred anyway, we don't care about render hints.
220                     shadowPainter->setFont(p->font());
221                     shadowPainter->setPen(ctxShadow->m_color);
222                     shadowPainter->drawText(pt, string, flags, run.expansion());
223                     ctxShadow->endShadowLayer(ctx);
224                 }
225             }
226         } else if (ctx->textDrawingMode() & TextModeStroke) {
227             if (ctxShadow->m_type != ContextShadow::BlurShadow) {
228                 p->translate(ctxShadow->offset());
229                 p->strokePath(textStrokePath, QPen(ctxShadow->m_color));
230                 p->translate(-ctxShadow->offset());
231             } else {
232                 QFontMetrics fm(font);
233                 QRectF boundingRect(pt.x(), point.y() - fm.ascent(), fm.width(string, -1, flags), fm.height());
234                 QPainter* shadowPainter = ctxShadow->beginShadowLayer(ctx, boundingRect);
235                 if (shadowPainter) {
236                     // Since it will be blurred anyway, we don't care about render hints.
237                     shadowPainter->setFont(p->font());
238                     shadowPainter->strokePath(textStrokePath, QPen(ctxShadow->m_color));
239                     ctxShadow->endShadowLayer(ctx);
240                 }
241             }
242         }
243     }
244 
245     if (ctx->textDrawingMode() & TextModeStroke)
246         p->strokePath(textStrokePath, textStrokePen);
247 
248     if (ctx->textDrawingMode() & TextModeFill) {
249         QPen previousPen = p->pen();
250         p->setPen(textFillPen);
251         p->drawText(pt, string, flags, run.expansion());
252         p->setPen(previousPen);
253     }
254 }
255 
drawComplexText(GraphicsContext * ctx,const TextRun & run,const FloatPoint & point,int from,int to) const256 void Font::drawComplexText(GraphicsContext* ctx, const TextRun& run, const FloatPoint& point, int from, int to) const
257 {
258     drawTextCommon(ctx, run, point, from, to, font(), /* isComplexText = */true);
259 }
260 
floatWidthForComplexText(const TextRun & run,HashSet<const SimpleFontData * > *,GlyphOverflow *) const261 float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>*, GlyphOverflow*) const
262 {
263     if (!primaryFont()->platformData().size())
264         return 0;
265 
266     if (!run.length())
267         return 0;
268 
269     if (run.length() == 1 && treatAsSpace(run[0]))
270         return QFontMetrics(font()).width(space) + run.expansion();
271 
272     String sanitized = Font::normalizeSpaces(run.characters(), run.length());
273     QString string = fromRawDataWithoutRef(sanitized);
274 
275     int w = QFontMetrics(font()).width(string);
276     // WebKit expects us to ignore word spacing on the first character (as opposed to what Qt does)
277     if (treatAsSpace(run[0]))
278         w -= m_wordSpacing;
279 
280     return w + run.expansion();
281 }
282 
offsetForPositionForComplexText(const TextRun & run,float position,bool) const283 int Font::offsetForPositionForComplexText(const TextRun& run, float position, bool) const
284 {
285     String sanitized = Font::normalizeSpaces(run.characters(), run.length());
286     QString string = fromRawDataWithoutRef(sanitized);
287 
288     QTextLayout layout(string, font());
289     QTextLine line = setupLayout(&layout, run);
290     return line.xToCursor(position);
291 }
292 
selectionRectForComplexText(const TextRun & run,const FloatPoint & pt,int h,int from,int to) const293 FloatRect Font::selectionRectForComplexText(const TextRun& run, const FloatPoint& pt, int h, int from, int to) const
294 {
295     String sanitized = Font::normalizeSpaces(run.characters(), run.length());
296     QString string = fromRawDataWithoutRef(sanitized);
297 
298     QTextLayout layout(string, font());
299     QTextLine line = setupLayout(&layout, run);
300 
301     float x1 = line.cursorToX(from);
302     float x2 = line.cursorToX(to);
303     if (x2 < x1)
304         qSwap(x1, x2);
305 
306     return FloatRect(pt.x() + x1, pt.y(), x2 - x1, h);
307 }
308 
canReturnFallbackFontsForComplexText()309 bool Font::canReturnFallbackFontsForComplexText()
310 {
311     return false;
312 }
313 
drawEmphasisMarksForComplexText(GraphicsContext *,const TextRun &,const AtomicString &,const FloatPoint &,int,int) const314 void Font::drawEmphasisMarksForComplexText(GraphicsContext* /* context */, const TextRun& /* run */, const AtomicString& /* mark */, const FloatPoint& /* point */, int /* from */, int /* to */) const
315 {
316     notImplemented();
317 }
318 
319 #if HAVE(QRAWFONT)
drawGlyphs(GraphicsContext * context,const SimpleFontData * fontData,const GlyphBuffer & glyphBuffer,int from,int numGlyphs,const FloatPoint & point) const320 void Font::drawGlyphs(GraphicsContext* context, const SimpleFontData* fontData, const GlyphBuffer& glyphBuffer, int from, int numGlyphs, const FloatPoint& point) const
321 {
322     if (context->paintingDisabled())
323         return;
324 
325     bool shouldFill = context->textDrawingMode() & TextModeFill;
326     bool shouldStroke = context->textDrawingMode() & TextModeStroke;
327 
328     // Stroking text should always take the complex path.
329     ASSERT(!shouldStroke);
330 
331     // Shadowed text should always take the complex path.
332     ASSERT(context->contextShadow()->m_type == ContextShadow::NoShadow);
333 
334     if (!shouldFill && !shouldStroke)
335         return;
336 
337     QVector<quint32> glyphIndexes;
338     QVector<QPointF> positions;
339 
340     glyphIndexes.reserve(numGlyphs);
341     positions.reserve(numGlyphs);
342 
343     float x = 0;
344 
345     for (int i = 0; i < numGlyphs; ++i) {
346         Glyph glyph = glyphBuffer.glyphAt(from + i);
347         float advance = glyphBuffer.advanceAt(from + i);
348         if (!glyph)
349             continue;
350         glyphIndexes.append(glyph);
351         positions.append(QPointF(x, 0));
352         x += advance;
353     }
354 
355     QGlyphs qtGlyphs;
356     qtGlyphs.setGlyphIndexes(glyphIndexes);
357     qtGlyphs.setPositions(positions);
358     qtGlyphs.setFont(fontData->platformData().rawFont());
359 
360     QPainter* painter = context->platformContext();
361 
362     QPen previousPen = painter->pen();
363     painter->setPen(fillPenForContext(context));
364     painter->drawGlyphs(point, qtGlyphs);
365     painter->setPen(previousPen);
366 }
367 
canExpandAroundIdeographsInComplexText()368 bool Font::canExpandAroundIdeographsInComplexText()
369 {
370     return false;
371 }
372 
373 #else // !HAVE(QRAWFONT)
374 
drawSimpleText(GraphicsContext * ctx,const TextRun & run,const FloatPoint & point,int from,int to) const375 void Font::drawSimpleText(GraphicsContext* ctx, const TextRun& run, const FloatPoint& point, int from, int to) const
376 {
377     drawTextCommon(ctx, run, point, from, to, font(), /* isComplexText = */false);
378 }
379 
offsetForPositionForSimpleText(const TextRun & run,float position,bool includePartialGlyphs) const380 int Font::offsetForPositionForSimpleText(const TextRun& run, float position, bool includePartialGlyphs) const
381 {
382     String sanitized = Font::normalizeSpaces(run.characters(), run.length());
383     QString string = fromRawDataWithoutRef(sanitized);
384 
385     QFontMetrics fm(font());
386     float delta = position;
387     int curPos = 0;
388     do {
389         float charWidth = fm.width(string[curPos]);
390         delta -= charWidth;
391         if (includePartialGlyphs) {
392             if (delta + charWidth / 2 <= 0)
393                 break;
394         } else {
395             if (delta + charWidth <= 0)
396                 break;
397         }
398     } while (++curPos < string.size());
399 
400     return curPos;
401 }
402 
403 
floatWidthForSimpleText(const TextRun & run,GlyphBuffer * glyphBuffer,HashSet<const SimpleFontData * > * fallbackFonts,GlyphOverflow * glyphOverflow) const404 float Font::floatWidthForSimpleText(const TextRun& run, GlyphBuffer* glyphBuffer, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
405 {
406     if (!primaryFont()->platformData().size())
407         return 0;
408 
409     if (!run.length())
410         return 0;
411 
412     String sanitized = Font::normalizeSpaces(run.characters(), run.length());
413     QString string = fromRawDataWithoutRef(sanitized);
414 
415     int w = QFontMetrics(font()).width(string, -1, Qt::TextBypassShaping);
416 
417     // WebKit expects us to ignore word spacing on the first character (as opposed to what Qt does)
418     if (treatAsSpace(run[0]))
419         w -= m_wordSpacing;
420 
421     return w + run.expansion();
422 }
423 
424 
selectionRectForSimpleText(const TextRun & run,const FloatPoint & pt,int h,int from,int to) const425 FloatRect Font::selectionRectForSimpleText(const TextRun& run, const FloatPoint& pt, int h, int from, int to) const
426 {
427     String sanitized = Font::normalizeSpaces(run.characters(), run.length());
428     QString wholeText = fromRawDataWithoutRef(sanitized);
429     QString selectedText = fromRawDataWithoutRef(sanitized, from, qMin(to - from, wholeText.length() - from));
430 
431     int startX = QFontMetrics(font()).width(wholeText, from, Qt::TextBypassShaping);
432     int width = QFontMetrics(font()).width(selectedText, -1, Qt::TextBypassShaping);
433 
434     return FloatRect(pt.x() + startX, pt.y(), width, h);
435 }
436 
canExpandAroundIdeographsInComplexText()437 bool Font::canExpandAroundIdeographsInComplexText()
438 {
439     return false;
440 }
441 
primaryFontHasGlyphForCharacter(UChar32) const442 bool Font::primaryFontHasGlyphForCharacter(UChar32) const
443 {
444     notImplemented();
445     return true;
446 }
447 
emphasisMarkAscent(const AtomicString &) const448 int Font::emphasisMarkAscent(const AtomicString&) const
449 {
450     notImplemented();
451     return 0;
452 }
453 
emphasisMarkDescent(const AtomicString &) const454 int Font::emphasisMarkDescent(const AtomicString&) const
455 {
456     notImplemented();
457     return 0;
458 }
459 
emphasisMarkHeight(const AtomicString &) const460 int Font::emphasisMarkHeight(const AtomicString&) const
461 {
462     notImplemented();
463     return 0;
464 }
465 
drawEmphasisMarksForSimpleText(GraphicsContext *,const TextRun &,const AtomicString &,const FloatPoint &,int,int) const466 void Font::drawEmphasisMarksForSimpleText(GraphicsContext* /* context */, const TextRun& /* run */, const AtomicString& /* mark */, const FloatPoint& /* point */, int /* from */, int /* to */) const
467 {
468     notImplemented();
469 }
470 #endif // HAVE(QRAWFONT)
471 
font() const472 QFont Font::font() const
473 {
474     QFont f = primaryFont()->getQtFont();
475     f.setWeight(toQFontWeight(weight()));
476     f.setItalic(italic());
477     if (m_letterSpacing != 0)
478         f.setLetterSpacing(QFont::AbsoluteSpacing, m_letterSpacing);
479     if (m_wordSpacing != 0)
480         f.setWordSpacing(m_wordSpacing);
481     return f;
482 }
483 
484 
485 }
486 
487