1 /**
2  * This file is part of the html renderer for KDE.
3  *
4  * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
5  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
6  *           (C) 2000 Dirk Mueller (mueller@kde.org)
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  *
23  */
24 
25 #include "font.h"
26 
27 #include <config-khtml.h>
28 
29 #if HAVE_ALLOCA_H
30 #  include <alloca.h>
31 #  else
32 #  if HAVE_MALLOC_H
33 #    include <malloc.h>
34 #  else
35 #    include <stdlib.h>
36 #  endif
37 #endif
38 
39 #include "khtml_debug.h"
40 
41 #include <QHash>
42 #include <QFontDatabase>
43 #include <QtGlobal>
44 
45 // for SVG
46 #include "dom/dom_string.h"
47 
48 namespace khtml
49 {
50 
51 /** closes the current word and returns its width in pixels
52  * @param fm metrics of font to be used
53  * @param str string
54  * @param pos zero-indexed position within @p str upon which all other
55  *  indices are based
56  * @param wordStart relative index pointing to the position where the word started
57  * @param wordEnd relative index pointing one position after the word ended
58  * @return the width in pixels. May be 0 if @p wordStart and @p wordEnd were
59  *  equal.
60  */
closeWordAndGetWidth(const QFontMetrics & fm,const QChar * str,int pos,int wordStart,int wordEnd)61 static inline int closeWordAndGetWidth(const QFontMetrics &fm, const QChar *str, int pos,
62                                        int wordStart, int wordEnd)
63 {
64     if (wordEnd <= wordStart) {
65         return 0;
66     }
67 
68     return fm.width(QString::fromRawData(str + pos + wordStart, wordEnd - wordStart));
69 }
70 
drawDirectedText(QPainter * p,Qt::LayoutDirection d,int x,int y,const QString & str)71 static inline void drawDirectedText(QPainter *p, Qt::LayoutDirection d,
72                                     int x, int y, const QString &str)
73 {
74     QString qs = str;
75     // Qt doesn't have a function to force a direction,
76     // so we have to use a the unicode "RTO" character to
77     //  (no, setLayoutDirection isn't enough)
78     if (d == Qt::RightToLeft && str[0].direction() == QChar::DirL) {
79         qs.prepend(QChar(0x202E)); // RIGHT-TO-LEFT OVERRIDE
80     } else if (d == Qt::LeftToRight && str[0].direction() == QChar::DirR) {
81         qs.prepend(QChar(0x202D)); // LEFT-TO-RIGHT OVERRIDE
82     }
83 
84     Qt::LayoutDirection saveDir = p->layoutDirection();
85     p->setLayoutDirection(d);
86     // Qt 4 avoids rendering soft-hyphens by default.
87     // Append normal hyphen instead.
88     if (qs.endsWith(QChar(0xad))) {
89         qs.append(QChar('-'));
90     }
91     p->drawText(x, y, qs);
92     p->setLayoutDirection(saveDir);
93 }
94 
95 /** closes the current word and draws it
96  * @param p painter
97  * @param d text direction
98  * @param x current x position, will be inc-/decremented correctly according
99  *  to text direction
100  * @param y baseline of text
101  * @param widths list of widths; width of word is expected at position
102  *      wordStart
103  * @param str string
104  * @param pos zero-indexed position within @p str upon which all other
105  *  indices are based
106  * @param wordStart relative index pointing to the position where the word started,
107  *  will be set to wordEnd after function
108  * @param wordEnd relative index pointing one position after the word ended
109  */
closeAndDrawWord(QPainter * p,Qt::LayoutDirection d,int & x,int y,const short widths[],const QChar * str,int pos,int & wordStart,int wordEnd)110 static inline void closeAndDrawWord(QPainter *p, Qt::LayoutDirection d,
111                                     int &x, int y, const short widths[], const QChar *str, int pos,
112                                     int &wordStart, int wordEnd)
113 {
114     if (wordEnd <= wordStart) {
115         return;
116     }
117 
118     int width = widths[wordStart];
119     if (d == Qt::RightToLeft) {
120         x -= width;
121     }
122 
123     drawDirectedText(p, d, x, y, QString::fromRawData(str + pos + wordStart, wordEnd - wordStart));
124 
125     if (d != Qt::RightToLeft) {
126         x += width;
127     }
128 
129     wordStart = wordEnd;
130 }
131 
drawText(QPainter * p,int x,int y,const QChar * str,const int slen,int pos,int len,int toAdd,Qt::LayoutDirection d,int from,int to,QColor bg,int uy,int h,int deco) const132 void Font::drawText(QPainter *p, int x, int y, const QChar *str, const int slen, int pos, int len,
133                     int toAdd, Qt::LayoutDirection d, int from, int to, QColor bg, int uy, int h, int deco) const
134 {
135     if (!str || slen == 0) { // #188910
136         return;
137     }
138 
139     // ### fixme for RTL
140     if (!scFont && !letterSpacing && !wordSpacing && !toAdd && from == -1) {
141         // simply draw it
142         // Due to some unfounded cause QPainter::drawText traverses the
143         // *whole* string when painting, not only the specified
144         // [pos, pos + len) segment. This makes painting *extremely* slow for
145         // long render texts (in the order of several megabytes).
146         // Hence, only hand over the piece of text of the actual inline text box
147         drawDirectedText(p, d, x, y, QString::fromRawData(str + pos, len));
148     } else {
149         if (from < 0) {
150             from = 0;
151         }
152         if (to < 0) {
153             to = len;
154         }
155 
156         int numSpaces = 0;
157         if (toAdd) {
158             for (int i = 0; i < len; ++i)
159                 if (str[i + pos].category() == QChar::Separator_Space) {
160                     ++numSpaces;
161                 }
162         }
163 
164         if (d == Qt::RightToLeft) {
165             const int totWidth = width(str, slen, pos, len, false /*fast algo*/);
166             x += totWidth + toAdd;
167         }
168 
169         // ### sc could be optimized by only painting uppercase letters extra,
170         // and treat the rest WordWise, but I think it's not worth it.
171         // Somebody else may volunteer, and implement this ;-) (LS)
172 
173         // The mode determines whether the text is displayed character by
174         // character, word by word, or as a whole
175         enum { CharacterWise, WordWise, Whole }
176         mode = Whole;
177         if (!letterSpacing && !scFont && (wordSpacing || toAdd > 0)) {
178             mode = WordWise;
179         } else if (letterSpacing || scFont) {
180             mode = CharacterWise;
181         }
182 
183         const QFontMetrics &fm = cfi->fm;
184 
185         if (mode == Whole) {    // most likely variant is treated extra
186 
187             if (to < 0) {
188                 to = len;
189             }
190             const QString segStr(QString::fromRawData(str + pos + from, to - from));
191             const int preSegmentWidth = fm.width(QString::fromRawData(str + pos, len), from);
192             const int segmentWidth = fm.width(segStr);
193             const int eff_x = d == Qt::RightToLeft ? x - preSegmentWidth - segmentWidth
194                               : x + preSegmentWidth;
195 
196             // draw whole string segment, with optional background
197             if (bg.isValid()) {
198                 p->fillRect(eff_x, uy, segmentWidth, h, bg);
199             }
200             drawDirectedText(p, d, eff_x, y, segStr);
201             if (deco) {
202                 drawDecoration(p, eff_x, uy, y - uy, segmentWidth - 1, h, deco);
203             }
204             return;
205         }
206 
207         const QString qstr = QString::fromRawData(str, slen);
208         QString upper = qstr;
209         QFontMetrics sc_fm = fm;
210         if (scFont) {
211             // draw in small caps
212             upper = qstr.toUpper();
213             sc_fm = QFontMetrics(*scFont);
214         }
215 
216         // We are using two passes. In the first pass, the widths are collected,
217         // and stored. In the second, the actual characters are drawn.
218 
219         // For each letter in the text box, save the width of the character.
220         // When word-wise, only the first letter contains the width, but of the
221         // whole word.
222         short *const widthList = (short *)alloca((to + 1) * sizeof(short));
223 
224         // First pass: gather widths
225         int preSegmentWidth = 0;
226         int segmentWidth = 0;
227         int lastWordBegin = 0;
228         bool onSegment = from == 0;
229         for (int i = 0; i < to; ++i) {
230             if (i == from) {
231                 // Also close words on visibility boundary
232                 if (mode == WordWise) {
233                     const int width = closeWordAndGetWidth(fm, str, pos, lastWordBegin, i);
234 
235                     if (lastWordBegin < i) {
236                         widthList[lastWordBegin] = (short)width;
237                         lastWordBegin = i;
238                         preSegmentWidth += width;
239                     }
240                 }
241                 onSegment = true;
242             }
243 
244             const QChar ch = str[pos + i];
245             bool lowercase = (ch.category() == QChar::Letter_Lowercase);
246             bool is_space = (ch.category() == QChar::Separator_Space);
247             int chw = 0;
248             if (letterSpacing) {
249                 chw += letterSpacing;
250             }
251             if ((wordSpacing || toAdd) && is_space) {
252                 if (mode == WordWise) {
253                     const int width = closeWordAndGetWidth(fm, str, pos, lastWordBegin, i);
254                     if (lastWordBegin < i) {
255                         widthList[lastWordBegin] = (short)width;
256                         lastWordBegin = i;
257                         (onSegment ? segmentWidth : preSegmentWidth) += width;
258                     }
259                     ++lastWordBegin;        // ignore this space
260                 }
261                 chw += wordSpacing;
262                 if (numSpaces) {
263                     const int a = toAdd / numSpaces;
264                     chw += a;
265                     toAdd -= a;
266                     --numSpaces;
267                 }
268             }
269             if (is_space || mode == CharacterWise) {
270                 chw += lowercase ? sc_fm.charWidth(upper, pos + i) : fm.charWidth(qstr, pos + i);
271                 widthList[i] = (short)chw;
272 
273                 (onSegment ? segmentWidth : preSegmentWidth) += chw;
274             }
275 
276         }
277 
278         // close last word
279         Q_ASSERT(onSegment);
280         if (mode == WordWise) {
281             const int width = closeWordAndGetWidth(fm, str, pos, lastWordBegin, to);
282             segmentWidth += width;
283             widthList[lastWordBegin] = (short)width;
284         }
285 
286         if (d == Qt::RightToLeft) {
287             x -= preSegmentWidth;
288         } else {
289             x += preSegmentWidth;
290         }
291 
292         const int startx = d == Qt::RightToLeft ? x - segmentWidth : x;
293 
294         // optionally draw background
295         if (bg.isValid()) {
296             p->fillRect(startx, uy, segmentWidth, h, bg);
297         }
298 
299         // second pass: do the actual drawing
300         lastWordBegin = from;
301         for (int i = from; i < to; ++i) {
302             const QChar ch = str[pos + i];
303             bool lowercase = (ch.category() == QChar::Letter_Lowercase);
304             bool is_space = ch.category() == QChar::Separator_Space;
305             if (is_space) {
306                 if (mode == WordWise) {
307                     closeAndDrawWord(p, d, x, y, widthList, str, pos, lastWordBegin, i);
308                     ++lastWordBegin;    // jump over space
309                 }
310             }
311             if (is_space || mode == CharacterWise) {
312                 const int chw = widthList[i];
313                 if (d == Qt::RightToLeft) {
314                     x -= chw;
315                 }
316 
317                 if (scFont) {
318                     p->setFont(lowercase ? *scFont : cfi->f);
319                 }
320 
321                 drawDirectedText(p, d, x, y, QString((lowercase ? upper : qstr)[pos + i]));
322 #ifdef __GNUC__
323 #warning "Light bloatery"
324 #endif
325 
326                 if (d != Qt::RightToLeft) {
327                     x += chw;
328                 }
329             }
330 
331         }
332 
333         // don't forget to draw last word
334         if (mode == WordWise) {
335             closeAndDrawWord(p, d, x, y, widthList, str, pos, lastWordBegin, to);
336         }
337 
338         if (deco) {
339             drawDecoration(p, startx, uy, y - uy, segmentWidth - 1, h, deco);
340         }
341 
342         if (scFont) {
343             p->setFont(cfi->f);
344         }
345     }
346 }
347 
width(const QChar * chs,int,int pos,int len,bool fast,int start,int end,int toAdd) const348 int Font::width(const QChar *chs, int, int pos, int len, bool fast, int start, int end, int toAdd) const
349 {
350     if (!len) {
351         return 0;
352     }
353     int w = 0;
354 
355     // #### Qt 4 has a major speed regression : QFontMetrics::width() is around 15 times slower than Qt 3's.
356     // This is a great speed bottleneck as we are now spending up to 70% of the layout time in that method
357     // (compared to around 5% before).
358     // It as been reported to TT and acknowledged as issue N138867, but whether they intend to give it some
359     // care in the near future is unclear :-/
360     //
361     // #### Qt 4.4 RC is now *40* times slower than Qt 3.3. This is a complete and utter disaster.
362     // New issue about this as N203591, because the report from 2006 was apparently discarded.
363     //
364     // This issue is now mostly addressed, by first scanning strings for complex/combining unicode characters,
365     // and using the much faster, non-context-aware QFontMetrics::width(QChar) when none has been found.
366     //
367     // ... Even that, however, is ultra-slow with Qt4.5, so now we cache this width information as well.
368     QFontMetrics &fm = cfi->fm;
369     if (scFont) {
370         const QString qstr = QString::fromRawData(chs + pos, len);
371         const QString upper = qstr.toUpper();
372         const QChar *uc = qstr.unicode();
373         const QFontMetrics sc_fm(*scFont);
374         if (fast) {
375             for (int i = 0; i < len; ++i) {
376                 if ((uc + i)->category() == QChar::Letter_Lowercase) {
377                     w += sc_fm.width(upper[i]);
378                 } else {
379                     w += cfi->cachedCharWidth(qstr[i]);
380                 }
381             }
382         } else {
383             for (int i = 0; i < len; ++i) {
384                 if ((uc + i)->category() == QChar::Letter_Lowercase) {
385                     w += sc_fm.charWidth(upper, i);
386                 } else {
387                     w += fm.charWidth(qstr, i);
388                 }
389             }
390         }
391     } else {
392         if (fast) {
393             for (int i = 0; i < len; ++i) {
394                 w += cfi->cachedCharWidth(chs[i + pos]);
395             }
396         } else {
397             const QString qstr = QString::fromRawData(chs + pos, len);
398             w = fm.width(qstr);
399         }
400     }
401 
402     if (letterSpacing) {
403         w += len * letterSpacing;
404     }
405 
406     if (wordSpacing)
407         // add amount
408         for (int i = 0; i < len; ++i) {
409             if (chs[i + pos].category() == QChar::Separator_Space) {
410                 w += wordSpacing;
411             }
412         }
413 
414     if (toAdd) {
415         // first gather count of spaces
416         int numSpaces = 0;
417         for (int i = start; i != end; ++i)
418             if (chs[i].category() == QChar::Separator_Space) {
419                 ++numSpaces;
420             }
421         // distribute pixels evenly among spaces, but count only those within
422         // [pos, pos+len)
423         for (int i = start; numSpaces && i != pos + len; i++)
424             if (chs[i].category() == QChar::Separator_Space) {
425                 const int a = toAdd / numSpaces;
426                 if (i >= pos) {
427                     w += a;
428                 }
429                 toAdd -= a;
430                 --numSpaces;
431             }
432     }
433 
434     return w;
435 }
436 
charWidth(const QChar * chs,int slen,int pos,bool fast) const437 int Font::charWidth(const QChar *chs, int slen, int pos, bool fast) const
438 {
439     int w;
440     if (scFont && chs[pos].category() == QChar::Letter_Lowercase) {
441         QString str(chs, slen);
442         str[pos] = chs[pos].toUpper();
443         if (fast) {
444             w = QFontMetrics(*scFont).width(str[pos]);
445         } else {
446             w = QFontMetrics(*scFont).charWidth(str, pos);
447         }
448     } else {
449         if (fast) {
450             w = cfi->cachedCharWidth(chs[pos]);
451         } else {
452             w = cfi->fm.charWidth(QString::fromRawData(chs, slen), pos);
453         }
454     }
455     if (letterSpacing) {
456         w += letterSpacing;
457     }
458 
459     if (wordSpacing && (chs + pos)->category() == QChar::Separator_Space) {
460         w += wordSpacing;
461     }
462     return w;
463 }
464 
465 /**
466  We cache information on fonts, including what sizes they are scalable too,
467  and their metrics since getting that info out of Qt is slow
468 */
469 
470 struct CachedFontFamilyKey { // basically, FontDef minus the size (and for now SC)
471     QString family;
472     int     weight;
473     bool    italic;
474 
CachedFontFamilyKeykhtml::CachedFontFamilyKey475     CachedFontFamilyKey() {}
476 
477     // resolvedFamily is not actually what it claims to be, as it comes from css style.
478     // e.g. it could be something like "foo,Open Sans,Impact"
CachedFontFamilyKeykhtml::CachedFontFamilyKey479     CachedFontFamilyKey(const QString &resolvedFamily, int weight, bool italic) :
480         family(resolvedFamily), weight(weight), italic(italic)
481     {}
482 
operator ==khtml::CachedFontFamilyKey483     bool operator == (const CachedFontFamilyKey &other) const
484     {
485         return (family == other.family) &&
486                (weight == other.weight) &&
487                (italic == other.italic);
488     }
489 };
490 
qHash(const CachedFontFamilyKey & key)491 static inline uint qHash(const CachedFontFamilyKey &key)
492 {
493     return ::qHash(key.family) ^ ::qHash(key.weight) ^ ::qHash(key.italic);
494 }
495 
496 class CachedFontFamily
497 {
498 public:
499     CachedFontFamilyKey def;
500 
501     bool scaleable;
502     QList<int> sizes; // if bitmap.
503 
504     QHash<int, CachedFontInstance *> instances;
505     // Maps from pixel size.. This is a weak-reference --- the refcount
506     // on the CFI itself determines the lifetime
507 
508     CachedFontInstance *queryFont(int pixelSize);
509     void invalidateAllInstances();
510     void markAllInstancesAsValid();
511 };
512 
513 static QHash<CachedFontFamilyKey, CachedFontFamily *> *fontCache;
514 // This is a hash and not a cache since the top-level info is tiny,
515 // and the actual font instances have precise lifetime
516 
invalidateCachedFontFamily(const QString & familyName)517 void Font::invalidateCachedFontFamily(const QString &familyName)   // static
518 {
519     if (!fontCache) {
520         return;
521     }
522     QHash<CachedFontFamilyKey, CachedFontFamily *>::const_iterator i;
523     QHash<CachedFontFamilyKey, CachedFontFamily *>::const_iterator end = fontCache->constEnd();
524     for (i = fontCache->constBegin(); i != end; ++i) {
525         if (i.key().family.contains(familyName, Qt::CaseInsensitive)) {
526             i.value()->invalidateAllInstances();
527         }
528     }
529 }
530 
markAllCachedFontsAsValid()531 void Font::markAllCachedFontsAsValid() // static
532 {
533     if (!fontCache) {
534         return;
535     }
536     QHash<CachedFontFamilyKey, CachedFontFamily *>::const_iterator i;
537     QHash<CachedFontFamilyKey, CachedFontFamily *>::const_iterator end = fontCache->constEnd();
538     for (i = fontCache->constBegin(); i != end; ++i) {
539         i.value()->markAllInstancesAsValid();
540     }
541 }
542 
queryFamily(const QString & name,int weight,bool italic)543 CachedFontFamily *Font::queryFamily(const QString &name, int weight, bool italic)
544 {
545     if (!fontCache) {
546         fontCache = new QHash<CachedFontFamilyKey, CachedFontFamily *>;
547     }
548 
549     CachedFontFamilyKey key(name, weight, italic);
550 
551     CachedFontFamily *f = fontCache->value(key);
552     if (!f) {
553         // To query the sizes, we seem to have to make a font with right style to produce the stylestring
554         QFont font(name);
555         font.setItalic(italic);
556         font.setWeight(weight);
557         // Use resolved family name to query font database
558         const QFontInfo fontInfo(font);
559         QFontDatabase db;
560         const QString resolvedFamily = fontInfo.family();
561         const QString styleString = db.styleString(fontInfo);
562         f = new CachedFontFamily;
563         f->def       = key;
564         f->scaleable = db.isSmoothlyScalable(resolvedFamily, styleString);
565 
566         if (!f->scaleable) {
567             /* Cache size info */
568             f->sizes = db.smoothSizes(resolvedFamily, styleString);
569         }
570 
571         fontCache->insert(key, f);
572     }
573 
574     return f;
575 }
576 
queryFont(int pixelSize)577 CachedFontInstance *CachedFontFamily::queryFont(int pixelSize)
578 {
579     CachedFontInstance *cfi = instances.value(pixelSize);
580     if (!cfi) {
581         cfi = new CachedFontInstance(this, pixelSize);
582         instances.insert(pixelSize, cfi);
583     }
584     return cfi;
585 }
586 
invalidateAllInstances()587 void CachedFontFamily::invalidateAllInstances()
588 {
589     QHash<int, CachedFontInstance *>::const_iterator i;
590     QHash<int, CachedFontInstance *>::const_iterator end = instances.constEnd();
591     for (i = instances.constBegin(); i != end; ++i) {
592         i.value()->invalidate();
593     }
594 }
595 
markAllInstancesAsValid()596 void CachedFontFamily::markAllInstancesAsValid()
597 {
598     QHash<int, CachedFontInstance *>::const_iterator i;
599     QHash<int, CachedFontInstance *>::const_iterator end = instances.constEnd();
600     for (i = instances.constBegin(); i != end; ++i) {
601         i.value()->invalidated = false;
602     }
603 }
604 
CachedFontInstance(CachedFontFamily * p,int sz)605 CachedFontInstance::CachedFontInstance(CachedFontFamily *p, int sz):
606     f(p->def.family), fm(f), invalidated(false), parent(p), size(sz)
607 {
608     f.setItalic(p->def.italic);
609     f.setWeight(p->def.weight);
610     f.setPixelSize(sz);
611     fm = QFontMetrics(f);
612 
613     // Prepare metrics caches
614     for (int c = 0; c < 256; ++c) {
615         rows[c] = nullptr;
616     }
617 
618     ascent  = fm.ascent();
619     descent = fm.descent();
620     height  = fm.height();
621     lineSpacing = fm.lineSpacing();
622     xHeight = fm.xHeight();
623 
624     const QChar zeroChar((ushort)48);
625     if (!fm.inFont(zeroChar)) {
626         m_zeroCharWidth = -1;
627     } else {
628         m_zeroCharWidth = (int)cachedCharWidth(zeroChar);
629     }
630 }
631 
invalidate()632 void CachedFontInstance::invalidate()
633 {
634     QFont nf(f.family());
635     nf.setWeight(f.weight());
636     nf.setItalic(f.italic());
637     nf.setPixelSize(f.pixelSize());
638     f = nf;
639     invalidated = true;
640     fm = QFontMetrics(f);
641 
642     // Cleanup metrics caches
643     for (int c = 0; c < 256; ++c) {
644         delete rows[c];
645         rows[c] = nullptr;
646     }
647 
648     ascent  = fm.ascent();
649     descent = fm.descent();
650     height  = fm.height();
651     lineSpacing = fm.lineSpacing();
652     xHeight = fm.xHeight();
653 
654     const QChar zeroChar((ushort)48);
655     if (!fm.inFont(zeroChar)) {
656         m_zeroCharWidth = -1;
657     } else {
658         m_zeroCharWidth = (int)cachedCharWidth(zeroChar);
659     }
660 }
661 
~CachedFontInstance()662 CachedFontInstance::~CachedFontInstance()
663 {
664     for (int c = 0; c < 256; ++c) {
665         delete rows[c];
666     }
667     parent->instances.remove(size);
668 }
669 
calcAndCacheWidth(unsigned short codePoint)670 unsigned CachedFontInstance::calcAndCacheWidth(unsigned short codePoint)
671 {
672     unsigned rowNum = codePoint >> 8;
673     RowInfo *row = rows[rowNum];
674     if (!row) {
675         row = rows[rowNum] = new RowInfo();
676     }
677 
678     unsigned width = fm.width(QChar(codePoint));
679     return (row->widths[codePoint & 0xFF] = qMin(width, 0xFFu));
680 }
681 
update(int logicalDpiY) const682 void Font::update(int logicalDpiY) const
683 {
684     CachedFontFamily *family = queryFamily(fontDef.family, fontDef.weight, fontDef.italic);
685 
686     int size = fontDef.size;
687 
688     // ok, now some magic to get a nice unscaled font
689     // all other font properties should be set before this one!!!!
690     if (!family->scaleable) {
691         const QList<int> pointSizes = family->sizes;
692         // lets see if we find a nice looking font, which is not too far away
693         // from the requested one.
694         // qCDebug(KHTML_LOG) << "khtml::setFontSize family = " << f.family() << " size requested=" << size;
695         const float toPix = qMax(logicalDpiY, 96) / 72.0f;
696 
697         float diff = 1; // ### 100% deviation
698         float bestSize = 0;
699 
700         QList<int>::ConstIterator it = pointSizes.begin();
701         const QList<int>::ConstIterator itEnd = pointSizes.end();
702 
703         for (; it != itEnd; ++it) {
704             float newDiff = (((*it) * toPix) - float(size)) / float(size);
705             //qCDebug(KHTML_LOG) << "smooth font size: " << *it << " diff=" << newDiff;
706             if (newDiff < 0) {
707                 newDiff = -newDiff;
708             }
709             if (newDiff < diff) {
710                 diff = newDiff;
711                 bestSize = *it;
712             }
713         }
714         //qCDebug(KHTML_LOG) << "best smooth font size: " << bestSize << " diff=" << diff;
715         if (bestSize != 0 && diff < 0.2) { // 20% deviation, otherwise we use a scaled font...
716             size = static_cast<int>(bestSize * toPix);
717         }
718     }
719 
720     // make sure we don't bust up X11
721     // Also, Qt does not support sizing a QFont to zero.
722     size = qMax(1, qMin(255, size));
723 
724 //       qDebug("setting font to %s, italic=%d, weight=%d, size=%d", fontDef.family.toLatin1().constData(), fontDef.italic,
725 //         fontDef.weight, size );
726 
727     // Now request the font from the family
728     cfi = family->queryFont(size);
729 
730     // small caps
731     delete scFont;
732     scFont = nullptr;
733 
734     if (fontDef.smallCaps) {
735         scFont = new QFont(cfi->f);
736         scFont->setPixelSize(qMax(1, cfi->f.pixelSize() * 7 / 10));
737     }
738 }
739 
740 CachedFontInstance *Font::defaultCFI;
741 
initDefault()742 void Font::initDefault()
743 {
744     if (defaultCFI) {
745         return;
746     }
747 
748     // Create one for default family. It doesn't matter what size and DPI we use
749     // since this is only used for throwaway computations
750     // ### this may cache an instance we don't need though; but font family
751     // is extremely likely to be used
752     Font f;
753     f.fontDef.size = 16;
754     f.update(96);
755 
756     defaultCFI = f.cfi.get();
757     defaultCFI->ref();
758 }
759 
drawDecoration(QPainter * pt,int _tx,int _ty,int baseline,int width,int height,int deco) const760 void Font::drawDecoration(QPainter *pt, int _tx, int _ty, int baseline, int width, int height, int deco) const
761 {
762     Q_UNUSED(height);
763 
764     // thick lines on small fonts look ugly
765     const int thickness = cfi->height > 20 ? cfi->fm.lineWidth() : 1;
766     const QBrush brush = pt->pen().color();
767     if (deco & UNDERLINE) {
768         int underlineOffset = (cfi->height + baseline) / 2;
769         if (underlineOffset <= baseline) {
770             underlineOffset = baseline + 1;
771         }
772 
773         pt->fillRect(_tx, _ty + underlineOffset, width + 1, thickness, brush);
774     }
775     if (deco & OVERLINE) {
776         pt->fillRect(_tx, _ty, width + 1, thickness, brush);
777     }
778     if (deco & LINE_THROUGH) {
779         pt->fillRect(_tx, _ty + 2 * baseline / 3, width + 1, thickness, brush);
780     }
781 }
782 
783 // WebCore SVG
floatWidth(QChar * str,int pos,int len,int,int & charsConsumed,DOM::DOMString & glyphName) const784 float Font::floatWidth(QChar *str, int pos, int len, int /*extraCharsAvailable*/, int &charsConsumed, DOM::DOMString &glyphName) const
785 {
786     charsConsumed = len;
787     glyphName = "";
788     // ### see if svg can scan the string (cf. render_text.cpp - isSimpleChar()) to determine if fast algo can be used.
789     return width(str, 0, pos, len, false /*fast algorithm*/);
790 }
791 
floatWidth(QChar * str,int pos,int len) const792 float Font::floatWidth(QChar *str, int pos, int len) const
793 {
794     // For now, this is slow but correct...
795     // or rather it /would/ be correct if QFontMetricsF had charWidth();
796     // so instead the approximate width is used
797     QFontMetricsF fm(cfi->f);
798     return float(fm.width(QString::fromRawData(str + pos, len)));
799 }
800 
801 }
802 
803