diff --git a/khtml/rendering/font.cpp b/khtml/rendering/font.cpp
index b1619ccd23..e5b9701a57 100644
--- a/khtml/rendering/font.cpp
+++ b/khtml/rendering/font.cpp
@@ -1,525 +1,528 @@
/**
* This file is part of the html renderer for KDE.
*
* Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2000 Dirk Mueller (mueller@kde.org)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "font.h"
#include
#ifdef HAVE_ALLOCA_H
# include
# else
# ifdef HAVE_MALLOC_H
# include
# else
# include
# endif
#endif
#include
#include
#include
#include
#include
#include
using namespace khtml;
/** closes the current word and returns its width in pixels
* @param fm metrics of font to be used
* @param str string
* @param pos zero-indexed position within @p str upon which all other
* indices are based
* @param wordStart relative index pointing to the position where the word started
* @param wordEnd relative index pointing one position after the word ended
* @return the width in pixels. May be 0 if @p wordStart and @p wordEnd were
* equal.
*/
static inline int closeWordAndGetWidth(const QFontMetrics &fm, const QChar *str, int pos,
int wordStart, int wordEnd)
{
if (wordEnd <= wordStart) return 0;
return fm.width(QString::fromRawData(str + pos + wordStart, wordEnd - wordStart));
}
static inline void drawDirectedText(QPainter *p, Qt::LayoutDirection d,
int x, int y, const QString &str)
{
QString qs = str;
- if (d == Qt::RightToLeft)
+ // Qt doesn't have a function to force a direction,
+ // so we have to use a the unicode "RTO" character to
+ // (no, setLayoutDirection isn't enough)
+ if (d == Qt::RightToLeft && str[0].direction() == QChar::DirL)
+ {
+ qs.prepend(QChar(0x202E)); // RIGHT-TO-LEFT OVERRIDE
+ }
+ else if (d == Qt::LeftToRight && str[0].direction() == QChar::DirR)
{
- // Qt doesn't have a function to force a direction,
- // so we have to use a the unicode "RTO" character to
- // (no, setLayoutDirection isn't enough)
- if (str[0].direction() == QChar::DirL)
- qs.prepend(QChar(0x202E));
+ qs.prepend(QChar(0x202D)); // LEFT-TO-RIGHT OVERRIDE
}
Qt::LayoutDirection saveDir = p->layoutDirection();
p->setLayoutDirection(d);
p->drawText(x, y, qs);
p->setLayoutDirection(saveDir);
}
/** closes the current word and draws it
* @param p painter
* @param d text direction
* @param x current x position, will be inc-/decremented correctly according
* to text direction
* @param y baseline of text
* @param widths list of widths; width of word is expected at position
* wordStart
* @param str string
* @param pos zero-indexed position within @p str upon which all other
* indices are based
* @param wordStart relative index pointing to the position where the word started,
* will be set to wordEnd after function
* @param wordEnd relative index pointing one position after the word ended
*/
static inline void closeAndDrawWord(QPainter *p, Qt::LayoutDirection d,
int &x, int y, const short widths[], const QChar *str, int pos,
int &wordStart, int wordEnd)
{
if (wordEnd <= wordStart) return;
int width = widths[wordStart];
if (d == Qt::RightToLeft)
x -= width;
drawDirectedText( p, d, x, y, QString::fromRawData(str + pos + wordStart, wordEnd - wordStart) );
if (d != Qt::RightToLeft)
x += width;
wordStart = wordEnd;
}
void Font::drawText( QPainter *p, int x, int y, QChar *str, int slen, int pos, int len,
int toAdd, Qt::LayoutDirection d, int from, int to, QColor bg, int uy, int h, int deco ) const
{
if (!str) return;
QString qstr = QString::fromRawData(str, slen);
// ### fixme for RTL
if ( !scFont && !letterSpacing && !wordSpacing && !toAdd && from==-1 ) {
// simply draw it
// Due to some unfounded cause QPainter::drawText traverses the
// *whole* string when painting, not only the specified
// [pos, pos + len) segment. This makes painting *extremely* slow for
// long render texts (in the order of several megabytes).
// Hence, only hand over the piece of text of the actual inline text box
drawDirectedText( p, d, x, y, QString::fromRawData(str + pos, len) );
} else {
if (from < 0) from = 0;
if (to < 0) to = len;
int numSpaces = 0;
if ( toAdd ) {
for( int i = 0; i < len; ++i )
if ( str[i+pos].category() == QChar::Separator_Space )
++numSpaces;
}
const int totWidth = width( str, slen, pos, len );
if ( d == Qt::RightToLeft ) {
x += totWidth + toAdd;
}
QString upper = qstr;
QFontMetrics sc_fm = fm;
if ( scFont ) {
// draw in small caps
upper = qstr.toUpper();
sc_fm = QFontMetrics( *scFont );
}
// ### sc could be optimized by only painting uppercase letters extra,
// and treat the rest WordWise, but I think it's not worth it.
// Somebody else may volunteer, and implement this ;-) (LS)
// The mode determines whether the text is displayed character by
// character, word by word, or as a whole
enum { CharacterWise, WordWise, Whole }
mode = Whole;
if (!letterSpacing && !scFont && (wordSpacing || toAdd > 0))
mode = WordWise;
else if (letterSpacing || scFont)
mode = CharacterWise;
if (mode == Whole) { // most likely variant is treated extra
if (to < 0) to = len;
const QString segStr(QString::fromRawData(str + pos + from, to - from));
const int preSegmentWidth = fm.width(QString::fromRawData(str + pos, len), from);
const int segmentWidth = fm.width(segStr);
const int eff_x = d == Qt::RightToLeft ? x - preSegmentWidth - segmentWidth
: x + preSegmentWidth;
// draw whole string segment, with optional background
if ( bg.isValid() )
p->fillRect( eff_x, uy, segmentWidth, h, bg );
drawDirectedText( p, d, eff_x, y, segStr );
if (deco)
drawDecoration(p, eff_x, uy, y - uy, segmentWidth - 1, h, deco);
return;
}
// We are using two passes. In the first pass, the widths are collected,
// and stored. In the second, the actual characters are drawn.
// For each letter in the text box, save the width of the character.
// When word-wise, only the first letter contains the width, but of the
// whole word.
short* const widthList = (short *)alloca((to+1)*sizeof(short));
// First pass: gather widths
int preSegmentWidth = 0;
int segmentWidth = 0;
int lastWordBegin = 0;
bool onSegment = from == 0;
for( int i = 0; i < to; ++i ) {
if (i == from) {
// Also close words on visibility boundary
if (mode == WordWise) {
const int width = closeWordAndGetWidth(fm, str, pos, lastWordBegin, i);
if (lastWordBegin < i) {
widthList[lastWordBegin] = (short)width;
lastWordBegin = i;
preSegmentWidth += width;
}
}
onSegment = true;
}
const QChar ch = str[pos+i];
bool lowercase = (ch.category() == QChar::Letter_Lowercase);
bool is_space = (ch.category() == QChar::Separator_Space);
int chw = 0;
if ( letterSpacing )
chw += letterSpacing;
if ( (wordSpacing || toAdd) && is_space ) {
if (mode == WordWise) {
const int width = closeWordAndGetWidth(fm, str, pos, lastWordBegin, i);
if (lastWordBegin < i) {
widthList[lastWordBegin] = (short)width;
lastWordBegin = i;
(onSegment ? segmentWidth : preSegmentWidth) += width;
}
++lastWordBegin; // ignore this space
}
chw += wordSpacing;
if ( numSpaces ) {
const int a = toAdd/numSpaces;
chw += a;
toAdd -= a;
--numSpaces;
}
}
if (is_space || mode == CharacterWise) {
chw += lowercase ? sc_fm.charWidth( upper, pos+i ) : fm.charWidth( qstr, pos+i );
widthList[i] = (short)chw;
(onSegment ? segmentWidth : preSegmentWidth) += chw;
}
}
// close last word
Q_ASSERT(onSegment);
if (mode == WordWise) {
const int width = closeWordAndGetWidth(fm, str, pos, lastWordBegin, to);
segmentWidth += width;
widthList[lastWordBegin] = (short)width;
}
if (d == Qt::RightToLeft) x -= preSegmentWidth;
else x += preSegmentWidth;
const int startx = d == Qt::RightToLeft ? x-segmentWidth : x;
// optionally draw background
if ( bg.isValid() )
p->fillRect( startx, uy, segmentWidth, h, bg );
// second pass: do the actual drawing
lastWordBegin = from;
for( int i = from; i < to; ++i ) {
const QChar ch = str[pos+i];
bool lowercase = (ch.category() == QChar::Letter_Lowercase);
bool is_space = ch.category() == QChar::Separator_Space;
if ( is_space ) {
if (mode == WordWise) {
closeAndDrawWord(p, d, x, y, widthList, str, pos, lastWordBegin, i);
++lastWordBegin; // jump over space
}
}
if (is_space || mode == CharacterWise) {
const int chw = widthList[i];
if (d == Qt::RightToLeft)
x -= chw;
if ( scFont )
p->setFont( lowercase ? *scFont : f );
drawDirectedText( p, d, x, y, QString((lowercase ? upper : qstr)[pos+i]) );
#ifdef __GNUC__
#warning "Light bloatery"
#endif
if (d != Qt::RightToLeft)
x += chw;
}
}
// don't forget to draw last word
if (mode == WordWise) {
closeAndDrawWord(p, d, x, y, widthList, str, pos, lastWordBegin, to);
}
if (deco)
drawDecoration(p, startx, uy, y - uy, segmentWidth - 1, h, deco);
if ( scFont )
p->setFont( f );
}
}
int Font::width( QChar *chs, int, int pos, int len, int start, int end, int toAdd ) const
{
int w = 0;
// #### Qt 4 has a major speed regression : QFontMetrics::width() is around 15 times slower than Qt 3's.
// This is a great speed bottleneck as we are now spending up to 70% of the layout time in that method
// (compared to around 5% before).
// It as been reported to TT and acknowledged as issue N138867, but whether they intend to give it some
// care in the near future is unclear :-/
const QString qstr = QString::fromRawData(chs+pos, len);
if ( scFont ) {
const QString upper = qstr.toUpper();
const QChar *uc = qstr.unicode();
const QFontMetrics sc_fm( *scFont );
for ( int i = 0; i < len; ++i ) {
if ( (uc+i)->category() == QChar::Letter_Lowercase )
w += sc_fm.charWidth( upper, i );
else
w += fm.charWidth( qstr, i );
}
} else {
// ### might be a little inaccurate
w = fm.width( qstr );
}
if ( letterSpacing )
w += len*letterSpacing;
if ( wordSpacing )
// add amount
for( int i = 0; i < len; ++i ) {
if( chs[i+pos].category() == QChar::Separator_Space )
w += wordSpacing;
}
if ( toAdd ) {
// first gather count of spaces
int numSpaces = 0;
for( int i = start; i != end; ++i )
if ( chs[i].category() == QChar::Separator_Space )
++numSpaces;
// distribute pixels evenly among spaces, but count only those within
// [pos, pos+len)
for ( int i = start; numSpaces && i != pos + len; i++ )
if ( chs[i].category() == QChar::Separator_Space ) {
const int a = toAdd/numSpaces;
if ( i >= pos ) w += a;
toAdd -= a;
--numSpaces;
}
}
return w;
}
int Font::width( QChar *chs, int slen, int pos ) const
{
int w;
if ( scFont && chs[pos].category() == QChar::Letter_Lowercase ) {
QString str( chs, slen );
str[pos] = chs[pos].toUpper();
w = QFontMetrics( *scFont ).charWidth( str, pos );
} else {
w = fm.charWidth( QString::fromRawData( chs, slen ), pos );
}
if ( letterSpacing )
w += letterSpacing;
if ( wordSpacing && (chs+pos)->category() == QChar::Separator_Space )
w += wordSpacing;
return w;
}
/** Querying QFontDB whether something is scalable is expensive, so we cache. */
struct ScalKey
{
QString family;
int weight;
int italic;
ScalKey() {}
ScalKey(const QFont& font) :
family(font.family()), weight(font.weight()), italic(font.italic())
{}
bool operator == (const ScalKey& other) const {
return (family == other.family) &&
(weight == other.weight) &&
(italic == other.italic);
}
};
struct ScalInfo {
bool scaleable;
QList sizes;
};
uint qHash (const ScalKey& key) {
return qHash(key.family) ^ qHash(key.weight) ^ qHash(key.italic);
}
static QCache* scalCache;
bool Font::isFontScalable(QFontDatabase& db, const QFont& font)
{
if (!scalCache)
scalCache = new QCache(64);
ScalKey key(font);
ScalInfo* s = scalCache->object(key);
if (!s) {
QString styleString = db.styleString(font);
s = new ScalInfo;
s->scaleable = db.isSmoothlyScalable(font.family(), styleString);
if (!s->scaleable) {
/* Cache size info */
s->sizes = db.smoothSizes(font.family(), styleString);
}
scalCache->insert(key, s);
}
return s->scaleable;
}
void Font::update(int logicalDpiY) const
{
f.setFamily( fontDef.family.isEmpty() ? KHTMLGlobal::defaultHTMLSettings()->stdFontName() : fontDef.family );
f.setItalic( fontDef.italic );
f.setWeight( fontDef.weight );
QFontDatabase db;
int size = fontDef.size;
const int lDpiY = qMax(logicalDpiY, 96);
// ok, now some magic to get a nice unscaled font
// all other font properties should be set before this one!!!!
if( !isFontScalable(db, f) )
{
const QList pointSizes = scalCache->object(ScalKey(f))->sizes;
// lets see if we find a nice looking font, which is not too far away
// from the requested one.
// kDebug(6080) << "khtml::setFontSize family = " << f.family() << " size requested=" << size;
float diff = 1; // ### 100% deviation
float bestSize = 0;
QList::ConstIterator it = pointSizes.begin();
const QList::ConstIterator itEnd = pointSizes.end();
for( ; it != itEnd; ++it )
{
float newDiff = ((*it)*(lDpiY/72.) - float(size))/float(size);
//kDebug( 6080 ) << "smooth font size: " << *it << " diff=" << newDiff;
if(newDiff < 0) newDiff = -newDiff;
if(newDiff < diff)
{
diff = newDiff;
bestSize = *it;
}
}
//kDebug( 6080 ) << "best smooth font size: " << bestSize << " diff=" << diff;
if ( bestSize != 0 && diff < 0.2 ) // 20% deviation, otherwise we use a scaled font...
size = (int)((bestSize*lDpiY) / 72);
}
// make sure we don't bust up X11
// Also, Qt does not support sizing a QFont to zero.
size = qMax(1, qMin(255, size));
// qDebug("setting font to %s, italic=%d, weight=%d, size=%d", fontDef.family.toLatin1().constData(), fontDef.italic,
// fontDef.weight, size );
f.setPixelSize( size );
fm = QFontMetrics( f );
// small caps
delete scFont;
scFont = 0;
if ( fontDef.smallCaps ) {
scFont = new QFont( f );
scFont->setPixelSize( qMax(1, f.pixelSize()*7/10) );
}
}
void Font::drawDecoration(QPainter *pt, int _tx, int _ty, int baseline, int width, int height, int deco) const
{
Q_UNUSED(height);
// thick lines on small fonts look ugly
const int thickness = fm.height() > 20 ? fm.lineWidth() : 1;
const QBrush brush = pt->pen().color();
if (deco & UNDERLINE) {
int underlineOffset = ( fm.height() + baseline ) / 2;
if (underlineOffset <= baseline) underlineOffset = baseline+1;
pt->fillRect(_tx, _ty + underlineOffset, width + 1, thickness, brush );
}
if (deco & OVERLINE) {
pt->fillRect(_tx, _ty, width + 1, thickness, brush );
}
if (deco & LINE_THROUGH) {
pt->fillRect(_tx, _ty + 2*baseline/3, width + 1, thickness, brush );
}
}