Reviewed by Darin Adler.
[WebKit-https.git] / WebCore / platform / graphics / qt / FontQt.cpp
1 /*
2     Copyright (C) 2007 Trolltech ASA
3
4     This library is free software; you can redistribute it and/or
5     modify it under the terms of the GNU Library General Public
6     License as published by the Free Software Foundation; either
7     version 2 of the License, or (at your option) any later version.
8
9     This library is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12     Library General Public License for more details.
13
14     You should have received a copy of the GNU Library General Public License
15     along with this library; see the file COPYING.LIB.  If not, write to
16     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17     Boston, MA 02110-1301, USA.
18
19     This class provides all functionality needed for loading images, style sheets and html
20     pages from the web. It has a memory cache for these objects.
21 */
22 #include "config.h"
23 #include "Font.h"
24 #include "FontDescription.h"
25 #include "FontSelector.h"
26
27 #include "GraphicsContext.h"
28 #include <QTextLayout>
29 #include <QPainter>
30 #include <QFontMetrics>
31 #include <QFontInfo>
32 #include <qalgorithms.h>
33 #include <qdebug.h>
34
35 #include <limits.h>
36 namespace WebCore {
37
38 struct TextRunComponent {
39     TextRunComponent() : font(0) {}
40     TextRunComponent(const UChar *start, int length, bool rtl, const QFont *font, int offset, bool sc = false);
41     TextRunComponent(int spaces, bool rtl, const QFont *font, int offset);
42
43     inline bool isSpace() const { return spaces != 0; }
44
45     QString string;
46     const QFont *font;
47     int width;
48     int offset;
49     int spaces;
50 };
51
52 TextRunComponent::TextRunComponent(const UChar *start, int length, bool rtl, const QFont *f, int o, bool sc)
53     : string(reinterpret_cast<const QChar*>(start), length)
54     , font(f)
55     , offset(o)
56     , spaces(0)
57 {
58     if (sc)
59         string = string.toUpper();
60     string.prepend(rtl ? QChar(0x202e) : QChar(0x202d));
61     width = QFontMetrics(*font).width(string);
62 }
63
64 TextRunComponent::TextRunComponent(int s, bool rtl, const QFont *f, int o)
65     : string(s, QLatin1Char(' '))
66     , font(f)
67     , offset(o)
68     , spaces(s)
69 {
70     string.prepend(rtl ? QChar(0x202e) : QChar(0x202d));
71     width = spaces * QFontMetrics(*font).width(QLatin1Char(' '));
72 }
73
74
75 Font::Font()
76     : m_letterSpacing(0)
77     , m_wordSpacing(0)
78     , m_font()
79     , m_scFont()
80 {
81     QFontMetrics metrics(m_font);
82     m_spaceWidth = metrics.width(QLatin1Char(' '));
83     qreal pointsize = m_font.pointSizeF();
84     if (pointsize > 0)
85         m_scFont.setPointSizeF(pointsize*0.7);
86     else
87         m_scFont.setPixelSize(qRound(m_font.pixelSize()*.7));
88 }
89
90 Font::Font(const FontDescription& description, short letterSpacing, short wordSpacing)
91     : m_fontDescription(description)
92     , m_letterSpacing(letterSpacing)
93     , m_wordSpacing(wordSpacing)
94 {
95     const FontFamily* family = &description.family();
96     QString familyName;
97     while (family) {
98         familyName += family->family();
99         family = family->next();
100         if (family)
101             familyName += QLatin1Char(',');
102     }
103
104     m_font.setFamily(familyName);
105     m_font.setPixelSize(qRound(description.computedSize()));
106     m_font.setItalic(description.italic());
107     if (description.bold()) {
108         // Qt's Bold is 75, Webkit is 63.
109         m_font.setWeight(QFont::Bold);
110     } else {
111         m_font.setWeight(description.weight());
112     }
113     QFontMetrics metrics = QFontMetrics(m_font);
114     m_spaceWidth = metrics.width(QLatin1Char(' '));
115     m_scFont = m_font;
116     m_scFont.setPixelSize(qRound(description.computedSize()*.7));
117 }
118
119 Font::~Font()
120 {
121 }
122     
123 Font::Font(const Font& other)
124     : m_fontDescription(other.m_fontDescription)
125     , m_letterSpacing(other.m_letterSpacing)
126     , m_wordSpacing(other.m_wordSpacing)
127     , m_font(other.m_font)
128     , m_scFont(other.m_scFont)
129     , m_spaceWidth(other.m_spaceWidth)
130 {
131 }
132
133 Font& Font::operator=(const Font& other)
134 {
135     m_fontDescription = other.m_fontDescription;
136     m_letterSpacing = other.m_letterSpacing;
137     m_wordSpacing = other.m_wordSpacing;
138     m_font = other.m_font;
139     m_scFont = other.m_scFont;
140     m_spaceWidth = other.m_spaceWidth;
141     return *this;
142 }
143
144 bool Font::operator==(const Font& other) const
145 {
146     return m_fontDescription == other.m_fontDescription
147         && m_letterSpacing == other.m_letterSpacing
148         && m_wordSpacing == other.m_wordSpacing
149         && m_font == other.m_font
150         && m_scFont == other.m_scFont
151         && m_spaceWidth == other.m_spaceWidth;
152 }
153
154 void Font::update(PassRefPtr<FontSelector>) const
155 {
156     // don't think we need this
157 }
158
159 static int generateComponents(Vector<TextRunComponent, 1024>* components, const Font &font, const TextRun &run)
160 {
161 //     qDebug() << "generateComponents" << QString((const QChar *)run.characters(), run.length());
162     int letterSpacing = font.letterSpacing();
163     int wordSpacing = font.wordSpacing();
164     bool smallCaps = font.fontDescription().smallCaps();
165     int padding = run.padding();
166     int numSpaces = 0;
167     if (padding) {
168         for (int i = 0; i < run.length(); i++)
169             if (Font::treatAsSpace(run[i]))
170                 ++numSpaces;      
171     }
172
173     int offset = 0;
174     const QFont *f = &font.font();
175     if (letterSpacing || smallCaps) {
176         // need to draw every letter on it's own
177         int start = 0;
178         if (Font::treatAsSpace(run[0])) {
179             int add = 0;
180             if (numSpaces) {
181                 add = padding/numSpaces;
182                 padding -= add;
183                 --numSpaces;
184             }
185             components->append(TextRunComponent(1, run.rtl(), &font.font(), offset));
186             offset += add + letterSpacing + components->last().width;
187             start = 1;
188 //         qDebug() << "space at 0" << offset;
189         } else if (smallCaps) {
190             f = (QChar::category(run[0]) == QChar::Letter_Lowercase ? &font.scFont() : &font.font());
191         }
192         for (int i = 1; i < run.length(); ++i) {
193             uint ch = run[i];
194             if (QChar(ch).isHighSurrogate() && QChar(run[i-1]).isLowSurrogate())
195                 ch = QChar::surrogateToUcs4(ch, run[i-1]);
196             if (QChar(ch).isLowSurrogate() || QChar::category(ch) == QChar::Mark_NonSpacing)
197                 continue;
198             if (Font::treatAsSpace(run[i])) {
199                 int add = 0;
200 //                 qDebug() << "    treatAsSpace:" << i << start;
201                 if (i - start > 0) {
202                     components->append(TextRunComponent(run.characters() + start, i - start,
203                                                         run.rtl(), 
204                                                         f, offset, f == &font.scFont()));
205                     offset += components->last().width + letterSpacing;
206 //                     qDebug() << "   appending(1) " << components->last().string << components->last().width;
207                 }
208                 if (numSpaces) {
209                     add = padding/numSpaces;
210                     padding -= add;
211                     --numSpaces;
212                 }
213                 components->append(TextRunComponent(1, run.rtl(), &font.font(), offset));
214                 offset += wordSpacing + add + components->last().width + letterSpacing;
215                 start = i + 1;
216                 continue;
217             } else if (!letterSpacing) {
218 //                 qDebug() << i << char(run[i]) << (QChar::category(ch) == QChar::Letter_Lowercase) <<
219 //                     QFontInfo(*f).pointSizeF();
220                 if (QChar::category(ch) == QChar::Letter_Lowercase) {
221                     if (f == &font.scFont())
222                         continue;
223                 } else {
224                     if (f == &font.font())
225                         continue;
226                 }
227             }
228             if (i - start > 0) {
229                 components->append(TextRunComponent(run.characters() + start, i - start,
230                                                     run.rtl(), 
231                                                     f, offset, f == &font.scFont()));
232                 offset += components->last().width + letterSpacing;
233 //                 qDebug() << "   appending(2) " << components->last().string << components->last().width;
234             }
235             if (smallCaps)
236                 f = (QChar::category(ch) == QChar::Letter_Lowercase ? &font.scFont() : &font.font());
237             start = i;
238         }
239         if (run.length() - start > 0) {
240             components->append(TextRunComponent(run.characters() + start, run.length() - start,
241                                                 run.rtl(), 
242                                                 f, offset, f == &font.scFont()));
243             offset += components->last().width;
244 //             qDebug() << "   appending(3) " << components->last().string << components->last().width;
245         }
246         offset += letterSpacing;
247     } else {
248         int start = 0;
249         for (int i = 0; i < run.length(); ++i) {
250             if (Font::treatAsSpace(run[i])) {
251                 if (i - start > 0) {
252                     components->append(TextRunComponent(run.characters() + start, i - start,
253                                                         run.rtl(), 
254                                                         f, offset));
255                     offset += components->last().width;
256                 }
257                 int add = 0;
258                 if (numSpaces) {
259                     add = padding/numSpaces;
260                     padding -= add;
261                     --numSpaces;
262                 }
263                 components->append(TextRunComponent(1, run.rtl(), &font.font(), offset));
264                 offset += add + components->last().width;
265                 if (i)
266                     offset += wordSpacing;
267                 start = i + 1;
268             }
269         }
270         if (run.length() - start > 0) {
271             components->append(TextRunComponent(run.characters() + start, run.length() - start,
272                                                 run.rtl(), 
273                                                 f, offset));
274             offset += components->last().width;
275         }
276     }
277     return offset;
278 }
279
280 void Font::drawText(GraphicsContext* ctx, const TextRun& run, const FloatPoint& point, int from, int to) const
281 {
282     if (to < 0)
283         to = run.length();
284     QPainter *p = ctx->platformContext();
285     Color color = ctx->fillColor();
286     p->setPen(QColor(color));
287
288     Vector<TextRunComponent, 1024> components;
289     int w = generateComponents(&components, *this, run);
290
291     if (from > 0 || to < run.length()) {
292         FloatRect clip = selectionRectForText(run,
293                                               IntPoint(qRound(point.x()), qRound(point.y())),
294                                               QFontMetrics(m_font).height(), from, to);
295         QRectF rect(clip.x(), clip.y() - ascent(), clip.width(), clip.height());
296         p->save();
297         p->setClipRect(rect.toRect());
298     }
299
300     if (run.rtl()) {
301         for (int i = 0; i < components.size(); ++i) {
302             if (!components.at(i).isSpace()) {
303                 p->setFont(*components.at(i).font);
304                 QPointF pt(point.x() + w - components.at(i).offset - components.at(i).width, point.y());
305                 p->drawText(pt, components.at(i).string);
306             }
307         }
308     } else {
309         for (int i = 0; i < components.size(); ++i) {
310             if (!components.at(i).isSpace()) {
311                 p->setFont(*components.at(i).font);
312                 QPointF pt(point.x() + components.at(i).offset, point.y());
313                 p->drawText(pt, components.at(i).string);
314             }
315         }
316     }
317     if (from > 0 || to < run.length())
318         p->restore();
319 }
320
321 int Font::width(const TextRun& run) const
322 {
323     Vector<TextRunComponent, 1024> components;
324     int w = generateComponents(&components, *this, run);
325
326 //     qDebug() << "     width=" << w;
327     return w;
328 }
329
330 float Font::floatWidth(const TextRun& run) const
331 {
332     return width(run);
333 }
334
335 int Font::offsetForPosition(const TextRun& run, int position, bool includePartialGlyphs) const
336 {
337     Vector<TextRunComponent, 1024> components;
338     int w = generateComponents(&components, *this, run);
339
340     int offset = 0;
341     if (run.rtl()) {
342         for (int i = 0; i < components.size(); ++i) {
343             int xe = w - components.at(i).offset;
344             int xs = xe - components.at(i).width;
345             if (position >= xs) {
346                 QTextLayout layout(components.at(i).string, *components.at(i).font);
347                 layout.beginLayout();
348                 QTextLine l = layout.createLine();
349                 if (!l.isValid()) 
350                     return offset;
351                 
352                 l.setLineWidth(INT_MAX/256);
353                 layout.endLayout();
354
355                 if (position - xs >= l.width())
356                     return offset;
357                 int cursor = l.xToCursor(position - xs);
358                 if (cursor > 1)
359                     --cursor;
360                 return offset + cursor;
361             } else {
362                 offset += components.at(i).string.length() - 1;
363             }
364         }
365     } else {
366         for (int i = 0; i < components.size(); ++i) {
367             int xs = components.at(i).offset;
368             int xe = xs + components.at(i).width;
369             if (position <= xe) {
370                 QTextLayout layout(components.at(i).string, *components.at(i).font);
371                 layout.beginLayout();
372                 QTextLine l = layout.createLine();
373                 if (!l.isValid())
374                     return offset;
375                 
376                 l.setLineWidth(INT_MAX/256);
377                 layout.endLayout();
378
379                 if (position - xs >= l.width())
380                     return offset + components.at(i).string.length() - 1;
381                 int cursor = l.xToCursor(position - xs);
382                 if (cursor > 1)
383                     --cursor;
384                 return offset + cursor;
385             } else {
386                 offset += components.at(i).string.length() - 1;
387             }
388         }
389     }
390     return run.length();
391 }
392
393 static float cursorToX(const Vector<TextRunComponent, 1024>& components, int width, bool rtl, int cursor)
394 {
395     int start = 0;
396     for (int i = 0; i < components.size(); ++i) {
397         if (start + components.at(i).string.length() - 1 < cursor) {
398             start += components.at(i).string.length() - 1;
399             continue;
400         }
401         int xs = components.at(i).offset;
402         if (rtl)
403             xs = width - xs - components.at(i).width;
404         QTextLayout layout(components.at(i).string, *components.at(i).font);
405         layout.beginLayout();
406         QTextLine l = layout.createLine();
407         if (!l.isValid())
408             return 0;
409         
410         l.setLineWidth(INT_MAX/256);
411         layout.endLayout();
412         
413         return xs + l.cursorToX(cursor - start + 1);
414     }
415     return width;
416 }
417
418 FloatRect Font::selectionRectForText(const TextRun& run, const IntPoint& pt,
419                                      int h, int from, int to) const
420 {
421     Vector<TextRunComponent, 1024> components;
422     int w = generateComponents(&components, *this, run);
423
424     if (from == 0 && to == run.length())
425         return FloatRect(pt.x(), pt.y(), w, h);
426
427     float x1 = cursorToX(components, w, run.rtl(), from);
428     float x2 = cursorToX(components, w, run.rtl(), to);
429     if (x2 < x1)
430         qSwap(x1, x2);
431
432     return FloatRect(pt.x() + x1, pt.y(), x2 - x1, h);
433 }
434
435 bool Font::isFixedPitch() const
436 {
437     return QFontInfo(m_font).fixedPitch();
438 }
439
440 // Metrics that we query the FontFallbackList for.
441 int Font::ascent() const
442 {
443     return QFontMetrics(m_font).ascent();
444 }
445
446 int Font::descent() const
447 {
448     return QFontMetrics(m_font).descent();
449 }
450
451 int Font::lineSpacing() const
452 {
453     return QFontMetrics(m_font).lineSpacing();
454 }
455
456 float Font::xHeight() const
457 {
458     return QFontMetrics(m_font).xHeight();
459 }
460
461 unsigned Font::unitsPerEm() const
462 {
463     return 1; // FIXME!
464 }
465
466 int Font::spaceWidth() const
467 {
468     return m_spaceWidth;
469 }
470
471 }