FontQt.cpp   [plain text]


/*
    Copyright (C) 2007 Trolltech ASA

    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.

    This class provides all functionality needed for loading images, style sheets and html
    pages from the web. It has a memory cache for these objects.
*/
#include "config.h"
#include "Font.h"
#include "FontDescription.h"
#include "TextStyle.h"

#include "GraphicsContext.h"
#include <QTextLayout>
#include <QPainter>
#include <QFontMetrics>
#include <QFontInfo>
#include <qalgorithms.h>
#include <qdebug.h>

#include <limits.h>
namespace WebCore {

struct TextRunComponent {
    TextRunComponent() : font(0) {}
    TextRunComponent(const UChar *start, int length, bool rtl, const QFont *font, int offset, bool sc = false);
    TextRunComponent(int spaces, bool rtl, const QFont *font, int offset);

    inline bool isSpace() const { return spaces != 0; }

    QString string;
    const QFont *font;
    int width;
    int offset;
    int spaces;
};

TextRunComponent::TextRunComponent(const UChar *start, int length, bool rtl, const QFont *f, int o, bool sc)
    : string(reinterpret_cast<const QChar*>(start), length)
    , font(f)
    , offset(o)
    , spaces(0)
{
    if (sc)
        string = string.toUpper();
    string.prepend(rtl ? QChar(0x202e) : QChar(0x202d));
    width = QFontMetrics(*font).width(string);
}

TextRunComponent::TextRunComponent(int s, bool rtl, const QFont *f, int o)
    : string(s, QLatin1Char(' '))
    , font(f)
    , offset(o)
    , spaces(s)
{
    string.prepend(rtl ? QChar(0x202e) : QChar(0x202d));
    width = spaces * QFontMetrics(*font).width(QLatin1Char(' '));
}


Font::Font()
    : m_letterSpacing(0)
    , m_wordSpacing(0)
    , m_font()
    , m_scFont()
{
    QFontMetrics metrics(m_font);
    m_spaceWidth = metrics.width(QLatin1Char(' '));
    qreal pointsize = m_font.pointSizeF();
    if (pointsize > 0)
        m_scFont.setPointSizeF(pointsize*0.7);
    else
        m_scFont.setPixelSize(qRound(m_font.pixelSize()*.7));
}

Font::Font(const FontDescription& description, short letterSpacing, short wordSpacing)
    : m_fontDescription(description)
    , m_letterSpacing(letterSpacing)
    , m_wordSpacing(wordSpacing)
{
    const FontFamily* family = &description.family();
    QString familyName;
    while (family) {
        familyName += family->family();
        family = family->next();
        if (family)
            familyName += QLatin1Char(',');
    }

    m_font.setFamily(familyName);
    m_font.setPixelSize(qRound(description.computedSize()));
    m_font.setItalic(description.italic());
    if (description.bold()) {
        // Qt's Bold is 75, Webkit is 63.
        m_font.setWeight(QFont::Bold);
    } else {
        m_font.setWeight(description.weight());
    }
    QFontMetrics metrics = QFontMetrics(m_font);
    m_spaceWidth = metrics.width(QLatin1Char(' '));
    m_scFont = m_font;
    m_scFont.setPixelSize(qRound(description.computedSize()*.7));
}

Font::~Font()
{
}
    
Font::Font(const Font& other)
    : m_fontDescription(other.m_fontDescription)
    , m_letterSpacing(other.m_letterSpacing)
    , m_wordSpacing(other.m_wordSpacing)
    , m_font(other.m_font)
    , m_scFont(other.m_scFont)
    , m_spaceWidth(other.m_spaceWidth)
{
}

Font& Font::operator=(const Font& other)
{
    m_fontDescription = other.m_fontDescription;
    m_letterSpacing = other.m_letterSpacing;
    m_wordSpacing = other.m_wordSpacing;
    m_font = other.m_font;
    m_scFont = other.m_scFont;
    m_spaceWidth = other.m_spaceWidth;
    return *this;
}

void Font::update() const
{
    // don't think we need this
}

static int generateComponents(Vector<TextRunComponent, 1024>* components, const Font &font, const TextRun &run, const TextStyle &style)
{
//     qDebug() << "generateComponents" << QString((const QChar *)run.characters(), run.length());
    int letterSpacing = font.letterSpacing();
    int wordSpacing = font.wordSpacing();
    bool smallCaps = font.fontDescription().smallCaps();
    int padding = style.padding();
    int numSpaces = 0;
    if (padding) {
        for (int i = 0; i < run.length(); i++)
            if (Font::treatAsSpace(run[i]))
                ++numSpaces;      
    }

    int offset = 0;
    const QFont *f = &font.font();
    if (letterSpacing || smallCaps) {
        // need to draw every letter on it's own
        int start = 0;
        if (Font::treatAsSpace(run[0])) {
            int add = 0;
            if (numSpaces) {
                add = padding/numSpaces;
                padding -= add;
                --numSpaces;
            }
            components->append(TextRunComponent(1, style.rtl(), &font.font(), offset));
            offset += add + letterSpacing + components->last().width;
            start = 1;
//         qDebug() << "space at 0" << offset;
        } else if (smallCaps) {
            f = (QChar::category(run[0]) == QChar::Letter_Lowercase ? &font.scFont() : &font.font());
        }
        for (int i = 1; i < run.length(); ++i) {
            uint ch = run[i];
            if (QChar(ch).isHighSurrogate() && QChar(run[i-1]).isLowSurrogate())
                ch = QChar::surrogateToUcs4(ch, run[i-1]);
            if (QChar(ch).isLowSurrogate() || QChar::category(ch) == QChar::Mark_NonSpacing)
                continue;
            if (Font::treatAsSpace(run[i])) {
                int add = 0;
//                 qDebug() << "    treatAsSpace:" << i << start;
                if (i - start > 0) {
                    components->append(TextRunComponent(run.characters() + start, i - start,
                                                        style.rtl(), 
                                                        f, offset, f == &font.scFont()));
                    offset += components->last().width + letterSpacing;
//                     qDebug() << "   appending(1) " << components->last().string << components->last().width;
                }
                if (numSpaces) {
                    add = padding/numSpaces;
                    padding -= add;
                    --numSpaces;
                }
                components->append(TextRunComponent(1, style.rtl(), &font.font(), offset));
                offset += wordSpacing + add + components->last().width + letterSpacing;
                start = i + 1;
                continue;
            } else if (!letterSpacing) {
//                 qDebug() << i << char(run[i]) << (QChar::category(ch) == QChar::Letter_Lowercase) <<
//                     QFontInfo(*f).pointSizeF();
                if (QChar::category(ch) == QChar::Letter_Lowercase) {
                    if (f == &font.scFont())
                        continue;
                } else {
                    if (f == &font.font())
                        continue;
                }
            }
            if (i - start > 0) {
                components->append(TextRunComponent(run.characters() + start, i - start,
                                                    style.rtl(), 
                                                    f, offset, f == &font.scFont()));
                offset += components->last().width + letterSpacing;
//                 qDebug() << "   appending(2) " << components->last().string << components->last().width;
            }
            if (smallCaps)
                f = (QChar::category(ch) == QChar::Letter_Lowercase ? &font.scFont() : &font.font());
            start = i;
        }
        if (run.length() - start > 0) {
            components->append(TextRunComponent(run.characters() + start, run.length() - start,
                                                style.rtl(), 
                                                f, offset, f == &font.scFont()));
            offset += components->last().width;
//             qDebug() << "   appending(3) " << components->last().string << components->last().width;
        }
        offset += letterSpacing;
    } else {
        int start = 0;
        for (int i = 0; i < run.length(); ++i) {
            if (Font::treatAsSpace(run[i])) {
                if (i - start > 0) {
                    components->append(TextRunComponent(run.characters() + start, i - start,
                                                        style.rtl(), 
                                                        f, offset));
                    offset += components->last().width;
                }
                int add = 0;
                if (numSpaces) {
                    add = padding/numSpaces;
                    padding -= add;
                    --numSpaces;
                }
                components->append(TextRunComponent(1, style.rtl(), &font.font(), offset));
                offset += add + components->last().width;
                if (i)
                    offset += wordSpacing;
                start = i + 1;
            }
        }
        if (run.length() - start > 0) {
            components->append(TextRunComponent(run.characters() + start, run.length() - start,
                                                style.rtl(), 
                                                f, offset));
            offset += components->last().width;
        }
    }
    return offset;
}

void Font::drawText(GraphicsContext* ctx, const TextRun& run, const TextStyle& style, const FloatPoint& point, int from, int to) const
{
    if (to < 0)
        to = run.length();
    QPainter *p = ctx->platformContext();
    Color color = ctx->fillColor();
    p->setPen(QColor(color));

    Vector<TextRunComponent, 1024> components;
    int w = generateComponents(&components, *this, run, style);

    if (from > 0 || to < run.length()) {
        FloatRect clip = selectionRectForText(run, style,
                                              IntPoint(qRound(point.x()), qRound(point.y())),
                                              QFontMetrics(m_font).height(), from, to);
        QRectF rect(clip.x(), clip.y() - ascent(), clip.width(), clip.height());
        p->save();
        p->setClipRect(rect.toRect());
    }

    if (style.rtl()) {
        for (int i = 0; i < components.size(); ++i) {
            if (!components.at(i).isSpace()) {
                p->setFont(*components.at(i).font);
                QPointF pt(point.x() + w - components.at(i).offset - components.at(i).width, point.y());
                p->drawText(pt, components.at(i).string);
            }
        }
    } else {
        for (int i = 0; i < components.size(); ++i) {
            if (!components.at(i).isSpace()) {
                p->setFont(*components.at(i).font);
                QPointF pt(point.x() + components.at(i).offset, point.y());
                p->drawText(pt, components.at(i).string);
            }
        }
    }
    if (from > 0 || to < run.length())
        p->restore();
}

int Font::width(const TextRun& run, const TextStyle& style) const
{
    Vector<TextRunComponent, 1024> components;
    int w = generateComponents(&components, *this, run, style);

//     qDebug() << "     width=" << w;
    return w;
}

int Font::width(const TextRun& run) const
{
    return width(run, TextStyle());
}

float Font::floatWidth(const TextRun& run, const TextStyle& style) const
{
    return width(run, style);
}

float Font::floatWidth(const TextRun& run) const
{
    return width(run);
}

int Font::offsetForPosition(const TextRun& run, const TextStyle& style, int position, bool includePartialGlyphs) const
{
    Vector<TextRunComponent, 1024> components;
    int w = generateComponents(&components, *this, run, style);

    int offset = 0;
    if (style.rtl()) {
        for (int i = 0; i < components.size(); ++i) {
            int xe = w - components.at(i).offset;
            int xs = xe - components.at(i).width;
            if (position >= xs) {
                QTextLayout layout(components.at(i).string, *components.at(i).font);
                layout.beginLayout();
                QTextLine l = layout.createLine();
                if (!l.isValid()) 
                    return offset;
                
                l.setLineWidth(INT_MAX/256);
                layout.endLayout();

                if (position - xs >= l.width())
                    return offset;
                int cursor = l.xToCursor(position - xs);
                if (cursor > 1)
                    --cursor;
                return offset + cursor;
            } else {
                offset += components.at(i).string.length() - 1;
            }
        }
    } else {
        for (int i = 0; i < components.size(); ++i) {
            int xs = components.at(i).offset;
            int xe = xs + components.at(i).width;
            if (position <= xe) {
                QTextLayout layout(components.at(i).string, *components.at(i).font);
                layout.beginLayout();
                QTextLine l = layout.createLine();
                if (!l.isValid())
                    return offset;
                
                l.setLineWidth(INT_MAX/256);
                layout.endLayout();

                if (position - xs >= l.width())
                    return offset + components.at(i).string.length() - 1;
                int cursor = l.xToCursor(position - xs);
                if (cursor > 1)
                    --cursor;
                return offset + cursor;
            } else {
                offset += components.at(i).string.length() - 1;
            }
        }
    }
    return run.length();
}

static float cursorToX(const Vector<TextRunComponent, 1024>& components, int width,
                     const TextStyle& style, int cursor)
{
    int start = 0;
    for (int i = 0; i < components.size(); ++i) {
        if (start + components.at(i).string.length() - 1 < cursor) {
            start += components.at(i).string.length() - 1;
            continue;
        }
        int xs = components.at(i).offset;
        if (style.rtl())
            xs = width - xs - components.at(i).width;
        QTextLayout layout(components.at(i).string, *components.at(i).font);
        layout.beginLayout();
        QTextLine l = layout.createLine();
        if (!l.isValid())
            return 0;
        
        l.setLineWidth(INT_MAX/256);
        layout.endLayout();
        
        return xs + l.cursorToX(cursor - start + 1);
    }
    return width;
}

FloatRect Font::selectionRectForText(const TextRun& run, const TextStyle& style, const IntPoint& pt,
                                     int h, int from, int to) const
{
    Vector<TextRunComponent, 1024> components;
    int w = generateComponents(&components, *this, run, style);

    if (from == 0 && to == run.length())
        return FloatRect(pt.x(), pt.y(), w, h);

    float x1 = cursorToX(components, w, style, from);
    float x2 = cursorToX(components, w, style, to);
    if (x2 < x1)
        qSwap(x1, x2);

    return FloatRect(pt.x() + x1, pt.y(), x2 - x1, h);
}

bool Font::isFixedPitch() const
{
    return QFontInfo(m_font).fixedPitch();
}

// Metrics that we query the FontFallbackList for.
int Font::ascent() const
{
    return QFontMetrics(m_font).ascent();
}

int Font::descent() const
{
    return QFontMetrics(m_font).descent();
}

int Font::lineSpacing() const
{
    return QFontMetrics(m_font).lineSpacing();
}

float Font::xHeight() const
{
    return QFontMetrics(m_font).xHeight();
}

int Font::spaceWidth() const
{
    return m_spaceWidth;
}

}