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