From 38be0d13830efd2d98281c645c3a60afe05ffece Mon Sep 17 00:00:00 2001 From: Qt by Nokia Date: Wed, 27 Apr 2011 12:05:43 +0200 Subject: Initial import from the monolithic Qt. This is the beginning of revision history for this module. If you want to look at revision history older than this, please refer to the Qt Git wiki for how to use Git history grafting. At the time of writing, this wiki is located here: http://qt.gitorious.org/qt/pages/GitIntroductionWithQt If you have already performed the grafting and you don't see any history beyond this commit, try running "git log" with the "--follow" argument. Branched from the monolithic repo, Qt master branch, at commit 896db169ea224deb96c59ce8af800d019de63f12 --- src/gui/text/qabstractfontengine_p.h | 110 + src/gui/text/qabstractfontengine_qws.cpp | 776 ++++++ src/gui/text/qabstractfontengine_qws.h | 221 ++ src/gui/text/qabstracttextdocumentlayout.cpp | 623 +++++ src/gui/text/qabstracttextdocumentlayout.h | 150 ++ src/gui/text/qabstracttextdocumentlayout_p.h | 100 + src/gui/text/qcssparser.cpp | 2768 +++++++++++++++++++++ src/gui/text/qcssparser_p.h | 847 +++++++ src/gui/text/qcssscanner.cpp | 1146 +++++++++ src/gui/text/qfont.cpp | 3188 +++++++++++++++++++++++++ src/gui/text/qfont.h | 378 +++ src/gui/text/qfont_mac.cpp | 165 ++ src/gui/text/qfont_p.h | 290 +++ src/gui/text/qfont_qpa.cpp | 114 + src/gui/text/qfont_qws.cpp | 135 ++ src/gui/text/qfont_s60.cpp | 136 ++ src/gui/text/qfont_win.cpp | 166 ++ src/gui/text/qfont_x11.cpp | 368 +++ src/gui/text/qfontdatabase.cpp | 2715 +++++++++++++++++++++ src/gui/text/qfontdatabase.h | 180 ++ src/gui/text/qfontdatabase_mac.cpp | 466 ++++ src/gui/text/qfontdatabase_qpa.cpp | 394 +++ src/gui/text/qfontdatabase_qws.cpp | 975 ++++++++ src/gui/text/qfontdatabase_s60.cpp | 1091 +++++++++ src/gui/text/qfontdatabase_win.cpp | 1348 +++++++++++ src/gui/text/qfontdatabase_x11.cpp | 2146 +++++++++++++++++ src/gui/text/qfontengine.cpp | 1684 +++++++++++++ src/gui/text/qfontengine_coretext.mm | 843 +++++++ src/gui/text/qfontengine_coretext_p.h | 141 ++ src/gui/text/qfontengine_ft.cpp | 2074 ++++++++++++++++ src/gui/text/qfontengine_ft_p.h | 380 +++ src/gui/text/qfontengine_mac.mm | 1236 ++++++++++ src/gui/text/qfontengine_mac_p.h | 165 ++ src/gui/text/qfontengine_p.h | 452 ++++ src/gui/text/qfontengine_qpa.cpp | 691 ++++++ src/gui/text/qfontengine_qpa_p.h | 262 ++ src/gui/text/qfontengine_qpf.cpp | 1220 ++++++++++ src/gui/text/qfontengine_qpf_p.h | 299 +++ src/gui/text/qfontengine_qws.cpp | 665 ++++++ src/gui/text/qfontengine_s60.cpp | 569 +++++ src/gui/text/qfontengine_s60_p.h | 167 ++ src/gui/text/qfontengine_win.cpp | 1322 +++++++++++ src/gui/text/qfontengine_win_p.h | 161 ++ src/gui/text/qfontengine_x11.cpp | 1201 ++++++++++ src/gui/text/qfontengine_x11_p.h | 178 ++ src/gui/text/qfontenginedirectwrite.cpp | 635 +++++ src/gui/text/qfontenginedirectwrite_p.h | 130 + src/gui/text/qfontengineglyphcache_p.h | 98 + src/gui/text/qfontinfo.h | 88 + src/gui/text/qfontmetrics.cpp | 1819 ++++++++++++++ src/gui/text/qfontmetrics.h | 208 ++ src/gui/text/qfontsubset.cpp | 1743 ++++++++++++++ src/gui/text/qfontsubset_p.h | 99 + src/gui/text/qfragmentmap.cpp | 46 + src/gui/text/qfragmentmap_p.h | 888 +++++++ src/gui/text/qglyphs.cpp | 323 +++ src/gui/text/qglyphs.h | 107 + src/gui/text/qglyphs_p.h | 103 + src/gui/text/qpfutil.cpp | 66 + src/gui/text/qplatformfontdatabase_qpa.cpp | 291 +++ src/gui/text/qplatformfontdatabase_qpa.h | 108 + src/gui/text/qrawfont.cpp | 612 +++++ src/gui/text/qrawfont.h | 140 ++ src/gui/text/qrawfont_ft.cpp | 189 ++ src/gui/text/qrawfont_mac.cpp | 105 + src/gui/text/qrawfont_p.h | 132 ++ src/gui/text/qrawfont_win.cpp | 750 ++++++ src/gui/text/qstatictext.cpp | 733 ++++++ src/gui/text/qstatictext.h | 109 + src/gui/text/qstatictext_p.h | 203 ++ src/gui/text/qsyntaxhighlighter.cpp | 665 ++++++ src/gui/text/qsyntaxhighlighter.h | 112 + src/gui/text/qtextcontrol.cpp | 3148 ++++++++++++++++++++++++ src/gui/text/qtextcontrol_p.h | 312 +++ src/gui/text/qtextcontrol_p_p.h | 238 ++ src/gui/text/qtextcursor.cpp | 2561 ++++++++++++++++++++ src/gui/text/qtextcursor.h | 240 ++ src/gui/text/qtextcursor_p.h | 122 + src/gui/text/qtextdocument.cpp | 3044 ++++++++++++++++++++++++ src/gui/text/qtextdocument.h | 309 +++ src/gui/text/qtextdocument_p.cpp | 1724 ++++++++++++++ src/gui/text/qtextdocument_p.h | 408 ++++ src/gui/text/qtextdocumentfragment.cpp | 1224 ++++++++++ src/gui/text/qtextdocumentfragment.h | 92 + src/gui/text/qtextdocumentfragment_p.h | 236 ++ src/gui/text/qtextdocumentlayout.cpp | 3263 +++++++++++++++++++++++++ src/gui/text/qtextdocumentlayout_p.h | 119 + src/gui/text/qtextdocumentwriter.cpp | 372 +++ src/gui/text/qtextdocumentwriter.h | 93 + src/gui/text/qtextengine.cpp | 2845 ++++++++++++++++++++++ src/gui/text/qtextengine_mac.cpp | 656 +++++ src/gui/text/qtextengine_p.h | 643 +++++ src/gui/text/qtextformat.cpp | 3297 ++++++++++++++++++++++++++ src/gui/text/qtextformat.h | 976 ++++++++ src/gui/text/qtextformat_p.h | 111 + src/gui/text/qtexthtmlparser.cpp | 1905 +++++++++++++++ src/gui/text/qtexthtmlparser_p.h | 344 +++ src/gui/text/qtextimagehandler.cpp | 232 ++ src/gui/text/qtextimagehandler_p.h | 80 + src/gui/text/qtextlayout.cpp | 2929 +++++++++++++++++++++++ src/gui/text/qtextlayout.h | 259 ++ src/gui/text/qtextlist.cpp | 321 +++ src/gui/text/qtextlist.h | 94 + src/gui/text/qtextobject.cpp | 1785 ++++++++++++++ src/gui/text/qtextobject.h | 335 +++ src/gui/text/qtextobject_p.h | 114 + src/gui/text/qtextodfwriter.cpp | 837 +++++++ src/gui/text/qtextodfwriter_p.h | 115 + src/gui/text/qtextoption.cpp | 434 ++++ src/gui/text/qtextoption.h | 163 ++ src/gui/text/qtexttable.cpp | 1323 +++++++++++ src/gui/text/qtexttable.h | 145 ++ src/gui/text/qtexttable_p.h | 89 + src/gui/text/qzip.cpp | 1263 ++++++++++ src/gui/text/qzipreader_p.h | 124 + src/gui/text/qzipwriter_p.h | 116 + src/gui/text/text.pri | 245 ++ 117 files changed, 84493 insertions(+) create mode 100644 src/gui/text/qabstractfontengine_p.h create mode 100644 src/gui/text/qabstractfontengine_qws.cpp create mode 100644 src/gui/text/qabstractfontengine_qws.h create mode 100644 src/gui/text/qabstracttextdocumentlayout.cpp create mode 100644 src/gui/text/qabstracttextdocumentlayout.h create mode 100644 src/gui/text/qabstracttextdocumentlayout_p.h create mode 100644 src/gui/text/qcssparser.cpp create mode 100644 src/gui/text/qcssparser_p.h create mode 100644 src/gui/text/qcssscanner.cpp create mode 100644 src/gui/text/qfont.cpp create mode 100644 src/gui/text/qfont.h create mode 100644 src/gui/text/qfont_mac.cpp create mode 100644 src/gui/text/qfont_p.h create mode 100644 src/gui/text/qfont_qpa.cpp create mode 100644 src/gui/text/qfont_qws.cpp create mode 100644 src/gui/text/qfont_s60.cpp create mode 100644 src/gui/text/qfont_win.cpp create mode 100644 src/gui/text/qfont_x11.cpp create mode 100644 src/gui/text/qfontdatabase.cpp create mode 100644 src/gui/text/qfontdatabase.h create mode 100644 src/gui/text/qfontdatabase_mac.cpp create mode 100644 src/gui/text/qfontdatabase_qpa.cpp create mode 100644 src/gui/text/qfontdatabase_qws.cpp create mode 100644 src/gui/text/qfontdatabase_s60.cpp create mode 100644 src/gui/text/qfontdatabase_win.cpp create mode 100644 src/gui/text/qfontdatabase_x11.cpp create mode 100644 src/gui/text/qfontengine.cpp create mode 100644 src/gui/text/qfontengine_coretext.mm create mode 100644 src/gui/text/qfontengine_coretext_p.h create mode 100644 src/gui/text/qfontengine_ft.cpp create mode 100644 src/gui/text/qfontengine_ft_p.h create mode 100644 src/gui/text/qfontengine_mac.mm create mode 100644 src/gui/text/qfontengine_mac_p.h create mode 100644 src/gui/text/qfontengine_p.h create mode 100644 src/gui/text/qfontengine_qpa.cpp create mode 100644 src/gui/text/qfontengine_qpa_p.h create mode 100644 src/gui/text/qfontengine_qpf.cpp create mode 100644 src/gui/text/qfontengine_qpf_p.h create mode 100644 src/gui/text/qfontengine_qws.cpp create mode 100644 src/gui/text/qfontengine_s60.cpp create mode 100644 src/gui/text/qfontengine_s60_p.h create mode 100644 src/gui/text/qfontengine_win.cpp create mode 100644 src/gui/text/qfontengine_win_p.h create mode 100644 src/gui/text/qfontengine_x11.cpp create mode 100644 src/gui/text/qfontengine_x11_p.h create mode 100644 src/gui/text/qfontenginedirectwrite.cpp create mode 100644 src/gui/text/qfontenginedirectwrite_p.h create mode 100644 src/gui/text/qfontengineglyphcache_p.h create mode 100644 src/gui/text/qfontinfo.h create mode 100644 src/gui/text/qfontmetrics.cpp create mode 100644 src/gui/text/qfontmetrics.h create mode 100644 src/gui/text/qfontsubset.cpp create mode 100644 src/gui/text/qfontsubset_p.h create mode 100644 src/gui/text/qfragmentmap.cpp create mode 100644 src/gui/text/qfragmentmap_p.h create mode 100644 src/gui/text/qglyphs.cpp create mode 100644 src/gui/text/qglyphs.h create mode 100644 src/gui/text/qglyphs_p.h create mode 100644 src/gui/text/qpfutil.cpp create mode 100644 src/gui/text/qplatformfontdatabase_qpa.cpp create mode 100644 src/gui/text/qplatformfontdatabase_qpa.h create mode 100644 src/gui/text/qrawfont.cpp create mode 100644 src/gui/text/qrawfont.h create mode 100644 src/gui/text/qrawfont_ft.cpp create mode 100644 src/gui/text/qrawfont_mac.cpp create mode 100644 src/gui/text/qrawfont_p.h create mode 100644 src/gui/text/qrawfont_win.cpp create mode 100644 src/gui/text/qstatictext.cpp create mode 100644 src/gui/text/qstatictext.h create mode 100644 src/gui/text/qstatictext_p.h create mode 100644 src/gui/text/qsyntaxhighlighter.cpp create mode 100644 src/gui/text/qsyntaxhighlighter.h create mode 100644 src/gui/text/qtextcontrol.cpp create mode 100644 src/gui/text/qtextcontrol_p.h create mode 100644 src/gui/text/qtextcontrol_p_p.h create mode 100644 src/gui/text/qtextcursor.cpp create mode 100644 src/gui/text/qtextcursor.h create mode 100644 src/gui/text/qtextcursor_p.h create mode 100644 src/gui/text/qtextdocument.cpp create mode 100644 src/gui/text/qtextdocument.h create mode 100644 src/gui/text/qtextdocument_p.cpp create mode 100644 src/gui/text/qtextdocument_p.h create mode 100644 src/gui/text/qtextdocumentfragment.cpp create mode 100644 src/gui/text/qtextdocumentfragment.h create mode 100644 src/gui/text/qtextdocumentfragment_p.h create mode 100644 src/gui/text/qtextdocumentlayout.cpp create mode 100644 src/gui/text/qtextdocumentlayout_p.h create mode 100644 src/gui/text/qtextdocumentwriter.cpp create mode 100644 src/gui/text/qtextdocumentwriter.h create mode 100644 src/gui/text/qtextengine.cpp create mode 100644 src/gui/text/qtextengine_mac.cpp create mode 100644 src/gui/text/qtextengine_p.h create mode 100644 src/gui/text/qtextformat.cpp create mode 100644 src/gui/text/qtextformat.h create mode 100644 src/gui/text/qtextformat_p.h create mode 100644 src/gui/text/qtexthtmlparser.cpp create mode 100644 src/gui/text/qtexthtmlparser_p.h create mode 100644 src/gui/text/qtextimagehandler.cpp create mode 100644 src/gui/text/qtextimagehandler_p.h create mode 100644 src/gui/text/qtextlayout.cpp create mode 100644 src/gui/text/qtextlayout.h create mode 100644 src/gui/text/qtextlist.cpp create mode 100644 src/gui/text/qtextlist.h create mode 100644 src/gui/text/qtextobject.cpp create mode 100644 src/gui/text/qtextobject.h create mode 100644 src/gui/text/qtextobject_p.h create mode 100644 src/gui/text/qtextodfwriter.cpp create mode 100644 src/gui/text/qtextodfwriter_p.h create mode 100644 src/gui/text/qtextoption.cpp create mode 100644 src/gui/text/qtextoption.h create mode 100644 src/gui/text/qtexttable.cpp create mode 100644 src/gui/text/qtexttable.h create mode 100644 src/gui/text/qtexttable_p.h create mode 100644 src/gui/text/qzip.cpp create mode 100644 src/gui/text/qzipreader_p.h create mode 100644 src/gui/text/qzipwriter_p.h create mode 100644 src/gui/text/text.pri (limited to 'src/gui/text') diff --git a/src/gui/text/qabstractfontengine_p.h b/src/gui/text/qabstractfontengine_p.h new file mode 100644 index 0000000000..7a430b93bd --- /dev/null +++ b/src/gui/text/qabstractfontengine_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTFONTENGINE_P_H +#define QABSTRACTFONTENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qfontengine_p.h" +#include "qabstractfontengine_qws.h" + +QT_BEGIN_NAMESPACE + +class QCustomFontEngine; + +class QProxyFontEngine : public QFontEngine +{ + Q_OBJECT +public: + QProxyFontEngine(QAbstractFontEngine *engine, const QFontDef &def); + virtual ~QProxyFontEngine(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + virtual QImage alphaMapForGlyph(glyph_t); + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, QPainterPath *path, QTextItem::RenderFlags flags); + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual QFixed averageCharWidth() const; + virtual QFixed lineThickness() const; + virtual QFixed underlinePosition() const; + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const; + virtual qreal minRightBearing() const; + virtual int glyphCount() const; + + virtual bool canRender(const QChar *string, int len); + + virtual Type type() const { return Proxy; } + virtual const char *name() const { return "proxy engine"; } + +#if !defined(Q_WS_X11) && !defined(Q_WS_WIN) && !defined(Q_WS_MAC) && !defined(Q_OS_SYMBIAN) + virtual void draw(QPaintEngine *, qreal, qreal, const QTextItemInt &); +#endif + + inline QAbstractFontEngine::Capabilities capabilities() const + { return engineCapabilities; } + + bool drawAsOutline() const; + +private: + QAbstractFontEngine *engine; + QAbstractFontEngine::Capabilities engineCapabilities; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/text/qabstractfontengine_qws.cpp b/src/gui/text/qabstractfontengine_qws.cpp new file mode 100644 index 0000000000..7cb868f655 --- /dev/null +++ b/src/gui/text/qabstractfontengine_qws.cpp @@ -0,0 +1,776 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractfontengine_qws.h" +#include "qabstractfontengine_p.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QFontEngineInfoPrivate +{ +public: + inline QFontEngineInfoPrivate() + : pixelSize(0), weight(QFont::Normal), style(QFont::StyleNormal) + {} + + QString family; + qreal pixelSize; + int weight; + QFont::Style style; + QList writingSystems; +}; + +/*! + \class QFontEngineInfo + \preliminary + \brief The QFontEngineInfo class describes a specific font provided by a font engine plugin. + \since 4.3 + \ingroup qws + + \tableofcontents + + QFontEngineInfo is used to describe a request of a font to a font engine plugin as well as to + describe the actual fonts a plugin provides. + + \sa QAbstractFontEngine, QFontEnginePlugin +*/ + +/*! + Constructs a new empty QFontEngineInfo. +*/ +QFontEngineInfo::QFontEngineInfo() +{ + d = new QFontEngineInfoPrivate; +} + +/*! + Constructs a new QFontEngineInfo with the specified \a family. + The resulting object represents a freely scalable font with normal + weight and style. +*/ +QFontEngineInfo::QFontEngineInfo(const QString &family) +{ + d = new QFontEngineInfoPrivate; + d->family = family; +} + +/*! + Creates a new font engine info object with the same attributes as \a other. +*/ +QFontEngineInfo::QFontEngineInfo(const QFontEngineInfo &other) + : d(new QFontEngineInfoPrivate(*other.d)) +{ +} + +/*! + Assigns \a other to this font engine info object, and returns a reference + to this. +*/ +QFontEngineInfo &QFontEngineInfo::operator=(const QFontEngineInfo &other) +{ + *d = *other.d; + return *this; +} + +/*! + Destroys this QFontEngineInfo object. +*/ +QFontEngineInfo::~QFontEngineInfo() +{ + delete d; +} + +/*! + \property QFontEngineInfo::family + the family name of the font +*/ + +void QFontEngineInfo::setFamily(const QString &family) +{ + d->family = family; +} + +QString QFontEngineInfo::family() const +{ + return d->family; +} + +/*! + \property QFontEngineInfo::pixelSize + the pixel size of the font + + A pixel size of 0 represents a freely scalable font. +*/ + +void QFontEngineInfo::setPixelSize(qreal size) +{ + d->pixelSize = size; +} + +qreal QFontEngineInfo::pixelSize() const +{ + return d->pixelSize; +} + +/*! + \property QFontEngineInfo::weight + the weight of the font + + The value should be from the \l{QFont::Weight} enumeration. +*/ + +void QFontEngineInfo::setWeight(int weight) +{ + d->weight = weight; +} + +int QFontEngineInfo::weight() const +{ + return d->weight; +} + +/*! + \property QFontEngineInfo::style + the style of the font +*/ + +void QFontEngineInfo::setStyle(QFont::Style style) +{ + d->style = style; +} + +QFont::Style QFontEngineInfo::style() const +{ + return d->style; +} + +/*! + \property QFontEngineInfo::writingSystems + the writing systems supported by the font + + An empty list means that any writing system is supported. +*/ + +QList QFontEngineInfo::writingSystems() const +{ + return d->writingSystems; +} + +void QFontEngineInfo::setWritingSystems(const QList &writingSystems) +{ + d->writingSystems = writingSystems; +} + +class QFontEnginePluginPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QFontEnginePlugin) + + QString foundry; +}; + +/*! + \class QFontEnginePlugin + \preliminary + \brief The QFontEnginePlugin class is the base class for font engine factory plugins in Qt for Embedded Linux. + \since 4.3 + \ingroup qws + \ingroup plugins + + \tableofcontents + + QFontEnginePlugin is provided by font engine plugins to create + instances of subclasses of QAbstractFontEngine. + + The member functions create() and availableFontEngines() must be + implemented. + + \sa QAbstractFontEngine, QFontEngineInfo +*/ + +/*! + Creates a font engine plugin that creates font engines with the + specified \a foundry and \a parent. +*/ +QFontEnginePlugin::QFontEnginePlugin(const QString &foundry, QObject *parent) + : QObject(*new QFontEnginePluginPrivate, parent) +{ + Q_D(QFontEnginePlugin); + d->foundry = foundry; +} + +/*! + Destroys this font engine plugin. +*/ +QFontEnginePlugin::~QFontEnginePlugin() +{ +} + +/*! + Returns a list of foundries the font engine plugin provides. + The default implementation returns the foundry specified with the constructor. +*/ +QStringList QFontEnginePlugin::keys() const +{ + Q_D(const QFontEnginePlugin); + return QStringList(d->foundry); +} + +/*! + \fn QAbstractFontEngine *QFontEnginePlugin::create(const QFontEngineInfo &info) + + Implemented in subclasses to create a new font engine that provides a font that + matches \a info. +*/ + +/*! + \fn QList QFontEnginePlugin::availableFontEngines() const + + Implemented in subclasses to return a list of QFontEngineInfo objects that represents all font + engines the plugin can create. +*/ + +class QAbstractFontEnginePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QAbstractFontEngine) +public: +}; + +//The class is|provides|contains|specifies... +/*! + \class QAbstractFontEngine + \preliminary + \brief The QAbstractFontEngine class is the base class for font engine plugins in Qt for Embedded Linux. + \since 4.3 + \ingroup qws + + \tableofcontents + + QAbstractFontEngine is implemented by font engine plugins through QFontEnginePlugin. + + \sa QFontEnginePlugin, QFontEngineInfo +*/ + +/*! + \enum QAbstractFontEngine::Capability + + This enum describes the capabilities of a font engine. + + \value CanRenderGlyphs_Gray The font engine can render individual glyphs into 8 bpp images. + \value CanRenderGlyphs_Mono The font engine can render individual glyphs into 1 bpp images. + \value CanRenderGlyphs The font engine can render individual glyphs into images. + \value CanOutlineGlyphs The font engine can convert glyphs to painter paths. +*/ + +/*! + \enum QAbstractFontEngine::FontProperty + + This enum describes the properties of a font provided by a font engine. + + \value Ascent The ascent of the font, specified as a 26.6 fixed point value. + \value Descent The descent of the font, specified as a 26.6 fixed point value. + \value Leading The leading of the font, specified as a 26.6 fixed point value. + \value XHeight The 'x' height of the font, specified as a 26.6 fixed point value. + \value AverageCharWidth The average character width of the font, specified as a 26.6 fixed point value. + \value LineThickness The thickness of the underline and strikeout lines for the font, specified as a 26.6 fixed point value. + \value UnderlinePosition The distance from the base line to the underline position for the font, specified as a 26.6 fixed point value. + \value MaxCharWidth The width of the widest character in the font, specified as a 26.6 fixed point value. + \value MinLeftBearing The minimum left bearing of the font, specified as a 26.6 fixed point value. + \value MinRightBearing The maximum right bearing of the font, specified as a 26.6 fixed point value. + \value GlyphCount The number of glyphs in the font, specified as an integer value. + \value CacheGlyphsHint A boolean value specifying whether rendered glyphs should be cached by Qt. + \value OutlineGlyphsHint A boolean value specifying whether the font engine prefers outline drawing over image rendering for uncached glyphs. +*/ + +/*! + \enum QAbstractFontEngine::TextShapingFlag + + This enum describes flags controlling conversion of characters to glyphs and their metrics. + + \value RightToLeft The text is used in a right-to-left context. + \value ReturnDesignMetrics Return font design metrics instead of pixel metrics. +*/ + +/*! + \typedef QAbstractFontEngine::Fixed + + This type is \c int, interpreted as a 26.6 fixed point value. +*/ + +/*! + \class QAbstractFontEngine::GlyphMetrics + \brief QAbstractFontEngine::GlyphMetrics defines the metrics of a single glyph. + \preliminary + \since 4.3 +*/ + +/*! + \variable QAbstractFontEngine::GlyphMetrics::x + + The horizontal offset from the origin. +*/ + +/*! + \fn QAbstractFontEngine::GlyphMetrics::GlyphMetrics() + + Constructs an empty glyph metrics object with all values + set to zero. +*/ + +/*! + \variable QAbstractFontEngine::GlyphMetrics::y + + The vertical offset from the origin (baseline). +*/ + +/*! + \variable QAbstractFontEngine::GlyphMetrics::width + + The width of the glyph. +*/ + +/*! + \variable QAbstractFontEngine::GlyphMetrics::height + + The height of the glyph. +*/ + +/*! + \variable QAbstractFontEngine::GlyphMetrics::advance + + The advance of the glyph. +*/ + +/*! + \class QAbstractFontEngine::FixedPoint + \brief QAbstractFontEngine::FixedPoint defines a point in the place using 26.6 fixed point precision. + \preliminary + \since 4.3 +*/ + +/*! + \variable QAbstractFontEngine::FixedPoint::x + + The x coordinate of this point. +*/ + +/*! + \variable QAbstractFontEngine::FixedPoint::y + + The y coordinate of this point. +*/ + +/*! + Constructs a new QAbstractFontEngine with the given \a parent. +*/ +QAbstractFontEngine::QAbstractFontEngine(QObject *parent) + : QObject(*new QAbstractFontEnginePrivate, parent) +{ +} + +/*! + Destroys this QAbstractFontEngine object. +*/ +QAbstractFontEngine::~QAbstractFontEngine() +{ +} + +/*! + \fn QAbstractFontEngine::Capabilities QAbstractFontEngine::capabilities() const + + Implemented in subclasses to specify the font engine's capabilities. The return value + may be cached by the caller and is expected not to change during the lifetime of the + font engine. +*/ + +/*! + \fn QVariant QAbstractFontEngine::fontProperty(FontProperty property) const + + Implemented in subclasses to return the value of the font attribute \a property. The return + value may be cached by the caller and is expected not to change during the lifetime of the font + engine. +*/ + +/*! + \fn bool QAbstractFontEngine::convertStringToGlyphIndices(const QChar *string, int length, uint *glyphs, int *numGlyphs, TextShapingFlags flags) const + + Implemented in subclasses to convert the characters specified by \a string and \a length to + glyph indicies, using \a flags. The glyph indicies should be returned in the \a glyphs array + provided by the caller. The maximum size of \a glyphs is specified by the value pointed to by \a + numGlyphs. If successful, the subclass implementation sets the value pointed to by \a numGlyphs + to the actual number of glyph indices generated, and returns true. Otherwise, e.g. if there is + not enough space in the provided \a glyphs array, it should set \a numGlyphs to the number of + glyphs needed for the conversion and return false. +*/ + +/*! + \fn void QAbstractFontEngine::getGlyphAdvances(const uint *glyphs, int numGlyphs, Fixed *advances, TextShapingFlags flags) const + + Implemented in subclasses to retrieve the advances of the array specified by \a glyphs and \a + numGlyphs, using \a flags. The result is returned in \a advances, which is allocated by the + caller and contains \a numGlyphs elements. +*/ + +/*! + \fn QAbstractFontEngine::GlyphMetrics QAbstractFontEngine::glyphMetrics(uint glyph) const + + Implemented in subclass to return the metrics for \a glyph. +*/ + +/*! + Implemented in subclasses to render the specified \a glyph into a \a buffer with the given \a depth , + \a bytesPerLine and \a height. + + Returns true if rendering succeeded, false otherwise. +*/ +bool QAbstractFontEngine::renderGlyph(uint glyph, int depth, int bytesPerLine, int height, uchar *buffer) +{ + Q_UNUSED(glyph) + Q_UNUSED(depth) + Q_UNUSED(bytesPerLine) + Q_UNUSED(height) + Q_UNUSED(buffer) + qWarning("QAbstractFontEngine: renderGlyph is not implemented in font plugin!"); + return false; +} + +/*! + Implemented in subclasses to add the outline of the glyphs specified by \a glyphs and \a + numGlyphs at the specified \a positions to the painter path \a path. +*/ +void QAbstractFontEngine::addGlyphOutlinesToPath(uint *glyphs, int numGlyphs, FixedPoint *positions, QPainterPath *path) +{ + Q_UNUSED(glyphs) + Q_UNUSED(numGlyphs) + Q_UNUSED(positions) + Q_UNUSED(path) + qWarning("QAbstractFontEngine: addGlyphOutlinesToPath is not implemented in font plugin!"); +} + +/* +bool QAbstractFontEngine::supportsExtension(Extension extension) const +{ + Q_UNUSED(extension) + return false; +} + +QVariant QAbstractFontEngine::extension(Extension extension, const QVariant &argument) +{ + Q_UNUSED(argument) + Q_UNUSED(extension) + return QVariant(); +} +*/ + +QProxyFontEngine::QProxyFontEngine(QAbstractFontEngine *customEngine, const QFontDef &def) + : engine(customEngine) +{ + fontDef = def; + engineCapabilities = engine->capabilities(); +} + +QProxyFontEngine::~QProxyFontEngine() +{ + delete engine; +} + +bool QProxyFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + + QVarLengthArray glyphIndicies(*nglyphs); + if (!engine->convertStringToGlyphIndices(str, len, glyphIndicies.data(), nglyphs, QAbstractFontEngine::TextShapingFlags(int(flags)))) + return false; + + // ### use memcopy instead + for (int i = 0; i < *nglyphs; ++i) { + glyphs->glyphs[i] = glyphIndicies[i]; + } + glyphs->numGlyphs = *nglyphs; + + recalcAdvances(glyphs, flags); + return true; +} + +void QProxyFontEngine::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + const int nglyphs = glyphs->numGlyphs; + + QVarLengthArray advances(nglyphs); + engine->getGlyphAdvances(glyphs->glyphs, nglyphs, advances.data(), QAbstractFontEngine::TextShapingFlags(int(flags))); + + + // ### use memcopy instead + for (int i = 0; i < nglyphs; ++i) { + glyphs->advances_x[i] = QFixed::fromFixed(advances[i]); + glyphs->advances_y[i] = 0; + } +} + + +static QImage alphaMapFromPath(QFontEngine *fe, glyph_t glyph) +{ + glyph_metrics_t gm = fe->boundingBox(glyph); + int glyph_x = qFloor(gm.x.toReal()); + int glyph_y = qFloor(gm.y.toReal()); + int glyph_width = qCeil((gm.x + gm.width).toReal()) - glyph_x; + int glyph_height = qCeil((gm.y + gm.height).toReal()) - glyph_y; + + if (glyph_width <= 0 || glyph_height <= 0) + return QImage(); + QFixedPoint pt; + pt.x = 0; + pt.y = -glyph_y; // the baseline + QPainterPath path; + QImage im(glyph_width + qAbs(glyph_x) + 4, glyph_height, QImage::Format_ARGB32_Premultiplied); + im.fill(Qt::transparent); + QPainter p(&im); + p.setRenderHint(QPainter::Antialiasing); + fe->addGlyphsToPath(&glyph, &pt, 1, &path, 0); + p.setPen(Qt::NoPen); + p.setBrush(Qt::black); + p.drawPath(path); + p.end(); + + QImage indexed(im.width(), im.height(), QImage::Format_Indexed8); + QVector colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; yglyphMetrics(glyph); + if (metrics.width <= 0 || metrics.height <= 0) + return QImage(); + + QImage img(metrics.width >> 6, metrics.height >> 6, QImage::Format_Indexed8); + + // ### we should have QImage::Format_GrayScale8 + static QVector colorMap; + if (colorMap.isEmpty()) { + colorMap.resize(256); + for (int i=0; i<256; ++i) + colorMap[i] = qRgba(0, 0, 0, i); + } + + img.setColorTable(colorMap); + + engine->renderGlyph(glyph, /*depth*/8, img.bytesPerLine(), img.height(), img.bits()); + + return img; +} + +void QProxyFontEngine::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (engineCapabilities & QAbstractFontEngine::CanOutlineGlyphs) + engine->addGlyphOutlinesToPath(glyphs, nglyphs, reinterpret_cast(positions), path); + else + QFontEngine::addGlyphsToPath(glyphs, positions, nglyphs, path, flags); +} + +glyph_metrics_t QProxyFontEngine::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs == 0) + return glyph_metrics_t(); + + QFixed w = 0; + for (int i = 0; i < glyphs.numGlyphs; ++i) + w += glyphs.effectiveAdvance(i); + + return glyph_metrics_t(0, -ascent(), w, ascent() + descent(), w, 0); +} + +glyph_metrics_t QProxyFontEngine::boundingBox(glyph_t glyph) +{ + glyph_metrics_t m; + + QAbstractFontEngine::GlyphMetrics metrics = engine->glyphMetrics(glyph); + m.x = QFixed::fromFixed(metrics.x); + m.y = QFixed::fromFixed(metrics.y); + m.width = QFixed::fromFixed(metrics.width); + m.height = QFixed::fromFixed(metrics.height); + m.xoff = QFixed::fromFixed(metrics.advance); + + return m; +} + +QFixed QProxyFontEngine::ascent() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::Ascent).toInt()); +} + +QFixed QProxyFontEngine::descent() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::Descent).toInt()); +} + +QFixed QProxyFontEngine::leading() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::Leading).toInt()); +} + +QFixed QProxyFontEngine::xHeight() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::XHeight).toInt()); +} + +QFixed QProxyFontEngine::averageCharWidth() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::AverageCharWidth).toInt()); +} + +QFixed QProxyFontEngine::lineThickness() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::LineThickness).toInt()); +} + +QFixed QProxyFontEngine::underlinePosition() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::UnderlinePosition).toInt()); +} + +qreal QProxyFontEngine::maxCharWidth() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::MaxCharWidth).toInt()).toReal(); +} + +qreal QProxyFontEngine::minLeftBearing() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::MinLeftBearing).toInt()).toReal(); +} + +qreal QProxyFontEngine::minRightBearing() const +{ + return QFixed::fromFixed(engine->fontProperty(QAbstractFontEngine::MinRightBearing).toInt()).toReal(); +} + +int QProxyFontEngine::glyphCount() const +{ + return engine->fontProperty(QAbstractFontEngine::GlyphCount).toInt(); +} + +bool QProxyFontEngine::canRender(const QChar *string, int len) +{ + QVarLengthArray glyphs(len); + int numGlyphs = len; + + if (!engine->convertStringToGlyphIndices(string, len, glyphs.data(), &numGlyphs, /*flags*/0)) + return false; + + for (int i = 0; i < numGlyphs; ++i) + if (!glyphs[i]) + return false; + + return true; +} + +void QProxyFontEngine::draw(QPaintEngine *p, qreal _x, qreal _y, const QTextItemInt &si) +{ + QPaintEngineState *pState = p->state; + QRasterPaintEngine *paintEngine = static_cast(p); + + QTransform matrix = pState->transform(); + matrix.translate(_x, _y); + QFixed x = QFixed::fromReal(matrix.dx()); + QFixed y = QFixed::fromReal(matrix.dy()); + + QVarLengthArray positions; + QVarLengthArray glyphs; + getGlyphPositions(si.glyphs, matrix, si.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + for(int i = 0; i < glyphs.size(); i++) { + QImage glyph = alphaMapForGlyph(glyphs[i]); + if (glyph.isNull()) + continue; + + if (glyph.format() != QImage::Format_Indexed8 + && glyph.format() != QImage::Format_Mono) + continue; + + QAbstractFontEngine::GlyphMetrics metrics = engine->glyphMetrics(glyphs[i]); + + int depth = glyph.format() == QImage::Format_Mono ? 1 : 8; + paintEngine->alphaPenBlt(glyph.bits(), glyph.bytesPerLine(), depth, + qRound(positions[i].x + QFixed::fromFixed(metrics.x)), + qRound(positions[i].y + QFixed::fromFixed(metrics.y)), + glyph.width(), glyph.height()); + } +} + +/* + * This is only called when we use the proxy fontengine directly (without sharing the rendered + * glyphs). So we prefer outline rendering over rendering of unshared glyphs. That decision is + * done in qfontdatabase_qws.cpp by looking at the ShareGlyphsHint and the pixel size of the font. + */ +bool QProxyFontEngine::drawAsOutline() const +{ + if (!(engineCapabilities & QAbstractFontEngine::CanOutlineGlyphs)) + return false; + + QVariant outlineHint = engine->fontProperty(QAbstractFontEngine::OutlineGlyphsHint); + return !outlineHint.isValid() || outlineHint.toBool(); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qabstractfontengine_qws.h b/src/gui/text/qabstractfontengine_qws.h new file mode 100644 index 0000000000..dfc15dcf5d --- /dev/null +++ b/src/gui/text/qabstractfontengine_qws.h @@ -0,0 +1,221 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTFONTENGINE_QWS_H +#define QABSTRACTFONTENGINE_QWS_H + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QFontEngineInfoPrivate; + +class Q_GUI_EXPORT QFontEngineInfo +{ +public: + QDOC_PROPERTY(QString family READ family WRITE setFamily) + QDOC_PROPERTY(qreal pixelSize READ pixelSize WRITE setPixelSize) + QDOC_PROPERTY(int weight READ weight WRITE setWeight) + QDOC_PROPERTY(QFont::Style style READ style WRITE setStyle) + QDOC_PROPERTY(QList writingSystems READ writingSystems WRITE setWritingSystems) + + QFontEngineInfo(); + explicit QFontEngineInfo(const QString &family); + QFontEngineInfo(const QFontEngineInfo &other); + QFontEngineInfo &operator=(const QFontEngineInfo &other); + ~QFontEngineInfo(); + + void setFamily(const QString &name); + QString family() const; + + void setPixelSize(qreal size); + qreal pixelSize() const; + + void setWeight(int weight); + int weight() const; + + void setStyle(QFont::Style style); + QFont::Style style() const; + + QList writingSystems() const; + void setWritingSystems(const QList &writingSystems); + +private: + QFontEngineInfoPrivate *d; +}; + +class QAbstractFontEngine; + +struct Q_GUI_EXPORT QFontEngineFactoryInterface : public QFactoryInterface +{ + virtual QAbstractFontEngine *create(const QFontEngineInfo &info) = 0; + virtual QList availableFontEngines() const = 0; +}; + +#define QFontEngineFactoryInterface_iid "com.trolltech.Qt.QFontEngineFactoryInterface" +Q_DECLARE_INTERFACE(QFontEngineFactoryInterface, QFontEngineFactoryInterface_iid) + +class QFontEnginePluginPrivate; + +class Q_GUI_EXPORT QFontEnginePlugin : public QObject, public QFontEngineFactoryInterface +{ + Q_OBJECT + Q_INTERFACES(QFontEngineFactoryInterface:QFactoryInterface) +public: + QFontEnginePlugin(const QString &foundry, QObject *parent = 0); + ~QFontEnginePlugin(); + + virtual QStringList keys() const; + + virtual QAbstractFontEngine *create(const QFontEngineInfo &info) = 0; + virtual QList availableFontEngines() const = 0; + +private: + Q_DECLARE_PRIVATE(QFontEnginePlugin) + Q_DISABLE_COPY(QFontEnginePlugin) +}; + +class QAbstractFontEnginePrivate; + +class Q_GUI_EXPORT QAbstractFontEngine : public QObject +{ + Q_OBJECT +public: + enum Capability { + CanOutlineGlyphs = 1, + CanRenderGlyphs_Mono = 2, + CanRenderGlyphs_Gray = 4, + CanRenderGlyphs = CanRenderGlyphs_Mono | CanRenderGlyphs_Gray + }; + Q_DECLARE_FLAGS(Capabilities, Capability) + + explicit QAbstractFontEngine(QObject *parent = 0); + ~QAbstractFontEngine(); + + typedef int Fixed; // 26.6 + + struct FixedPoint + { + Fixed x; + Fixed y; + }; + + struct GlyphMetrics + { + inline GlyphMetrics() + : x(0), y(0), width(0), height(0), + advance(0) {} + Fixed x; + Fixed y; + Fixed width; + Fixed height; + Fixed advance; + }; + + enum FontProperty { + Ascent, + Descent, + Leading, + XHeight, + AverageCharWidth, + LineThickness, + UnderlinePosition, + MaxCharWidth, + MinLeftBearing, + MinRightBearing, + GlyphCount, + + // hints + CacheGlyphsHint, + OutlineGlyphsHint + }; + + // keep in sync with QTextEngine::ShaperFlag!! + enum TextShapingFlag { + RightToLeft = 0x0001, + ReturnDesignMetrics = 0x0002 + }; + Q_DECLARE_FLAGS(TextShapingFlags, TextShapingFlag) + + virtual Capabilities capabilities() const = 0; + virtual QVariant fontProperty(FontProperty property) const = 0; + + virtual bool convertStringToGlyphIndices(const QChar *string, int length, uint *glyphs, int *numGlyphs, TextShapingFlags flags) const = 0; + + virtual void getGlyphAdvances(const uint *glyphs, int numGlyphs, Fixed *advances, TextShapingFlags flags) const = 0; + + virtual GlyphMetrics glyphMetrics(uint glyph) const = 0; + + virtual bool renderGlyph(uint glyph, int depth, int bytesPerLine, int height, uchar *buffer); + + virtual void addGlyphOutlinesToPath(uint *glyphs, int numGlyphs, FixedPoint *positions, QPainterPath *path); + + /* + enum Extension { + GetTrueTypeTable + }; + + virtual bool supportsExtension(Extension extension) const; + virtual QVariant extension(Extension extension, const QVariant &argument = QVariant()); + */ + +private: + Q_DECLARE_PRIVATE(QAbstractFontEngine) + Q_DISABLE_COPY(QAbstractFontEngine) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractFontEngine::Capabilities) +Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractFontEngine::TextShapingFlags) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/text/qabstracttextdocumentlayout.cpp b/src/gui/text/qabstracttextdocumentlayout.cpp new file mode 100644 index 0000000000..53ffaa4c6d --- /dev/null +++ b/src/gui/text/qabstracttextdocumentlayout.cpp @@ -0,0 +1,623 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include "qtextdocument_p.h" +#include "qtextengine_p.h" + +#include "qabstracttextdocumentlayout_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QAbstractTextDocumentLayout + \reentrant + + \brief The QAbstractTextDocumentLayout class is an abstract base + class used to implement custom layouts for QTextDocuments. + + \ingroup richtext-processing + + The standard layout provided by Qt can handle simple word processing + including inline images, lists and tables. + + Some applications, e.g., a word processor or a DTP application might need + more features than the ones provided by Qt's layout engine, in which case + you can subclass QAbstractTextDocumentLayout to provide custom layout + behavior for your text documents. + + An instance of the QAbstractTextDocumentLayout subclass can be installed + on a QTextDocument object with the + \l{QTextDocument::}{setDocumentLayout()} function. + + You can insert custom objects into a QTextDocument; see the + QTextObjectInterface class description for details. + + \sa QTextObjectInterface +*/ + +/*! + \class QTextObjectInterface + \brief The QTextObjectInterface class allows drawing of + custom text objects in \l{QTextDocument}s. + \since 4.5 + + A text object describes the structure of one or more elements in a + text document; for instance, images imported from HTML are + implemented using text objects. A text object knows how to lay out + and draw its elements when a document is being rendered. + + Qt allows custom text objects to be inserted into a document by + registering a custom \l{QTextCharFormat::objectType()}{object + type} with QTextCharFormat. A QTextObjectInterface must also be + implemented for this type and be + \l{QAbstractTextDocumentLayout::registerHandler()}{registered} + with the QAbstractTextDocumentLayout of the document. When the + object type is encountered while rendering a QTextDocument, the + intrinsicSize() and drawObject() functions of the interface are + called. + + The following list explains the required steps of inserting a + custom text object into a document: + + \list + \o Choose an \a objectType. The \a objectType is an integer with a + value greater or equal to QTextFormat::UserObject. + \o Create a QTextCharFormat object and set the object type to the + chosen type using the setObjectType() function. + \o Implement the QTextObjectInterface class. + \o Call QAbstractTextDocumentLayout::registerHandler() with an instance of your + QTextObjectInterface subclass to register your object type. + \o Insert QChar::ObjectReplacementCharacter with the aforementioned + QTextCharFormat of the chosen object type into the document. + As mentioned, the functions of QTextObjectInterface + \l{QTextObjectInterface::}{intrinsicSize()} and + \l{QTextObjectInterface::}{drawObject()} will then be called with the + QTextFormat as parameter whenever the replacement character is + encountered. + \endlist + + A class implementing a text object needs to inherit both QObject + and QTextObjectInterface. QObject must be the first class + inherited. For instance: + + \snippet examples/richtext/textobject/svgtextobject.h 1 + + The data of a text object is usually stored in the QTextCharFormat + using QTextCharFormat::setProperty(), and then retrieved with + QTextCharFormat::property(). + + \warning Copy and Paste operations ignore custom text objects. + + \sa {Text Object Example}, QTextCharFormat, QTextLayout +*/ + +/*! + \fn QTextObjectInterface::~QTextObjectInterface() + + Destroys this QTextObjectInterface. +*/ + +/*! + \fn virtual QSizeF QTextObjectInterface::intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) = 0 + + The intrinsicSize() function returns the size of the text object + represented by \a format in the given document (\a doc) at the + given position (\a posInDocument). + + The size calculated will be used for subsequent calls to + drawObject() for this \a format. + + \sa drawObject() +*/ + +/*! + \fn virtual void QTextObjectInterface::drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format) = 0 + + Draws this text object using the specified \a painter. + + The size of the rectangle, \a rect, to draw in is the size + previously calculated by intrinsicSize(). The rectangles position + is relative to the \a painter. + + You also get the document (\a doc) and the position (\a + posInDocument) of the \a format in that document. + + \sa intrinsicSize() +*/ + +/*! + \fn void QAbstractTextDocumentLayout::update(const QRectF &rect) + + This signal is emitted when the rectangle \a rect has been updated. + + Subclasses of QAbstractTextDocumentLayout should emit this signal when + the layout of the contents change in order to repaint. +*/ + +/*! + \fn void QAbstractTextDocumentLayout::updateBlock(const QTextBlock &block) + \since 4.4 + + This signal is emitted when the specified \a block has been updated. + + Subclasses of QAbstractTextDocumentLayout should emit this signal when + the layout of \a block has changed in order to repaint. +*/ + +/*! + \fn void QAbstractTextDocumentLayout::documentSizeChanged(const QSizeF &newSize) + + This signal is emitted when the size of the document layout changes to + \a newSize. + + Subclasses of QAbstractTextDocumentLayout should emit this signal when the + document's entire layout size changes. This signal is useful for widgets + that display text documents since it enables them to update their scroll + bars correctly. + + \sa documentSize() +*/ + +/*! + \fn void QAbstractTextDocumentLayout::pageCountChanged(int newPages) + + This signal is emitted when the number of pages in the layout changes; + \a newPages is the updated page count. + + Subclasses of QAbstractTextDocumentLayout should emit this signal when + the number of pages in the layout has changed. Changes to the page count + are caused by changes to the layout or the document content itself. + + \sa pageCount() +*/ + +/*! + \fn int QAbstractTextDocumentLayout::pageCount() const + + Returns the number of pages contained in the layout. + + \sa pageCountChanged() +*/ + +/*! + \fn QSizeF QAbstractTextDocumentLayout::documentSize() const + + Returns the total size of the document's layout. + + This information can be used by display widgets to update their scroll bars + correctly. + + \sa documentSizeChanged(), QTextDocument::pageSize +*/ + +/*! + \fn void QAbstractTextDocumentLayout::draw(QPainter *painter, const PaintContext &context) + + Draws the layout with the given \a painter using the given \a context. +*/ + +/*! + \fn int QAbstractTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const + + Returns the cursor postion for the given \a point with the specified + \a accuracy. Returns -1 if no valid cursor position was found. +*/ + +/*! + \fn void QAbstractTextDocumentLayout::documentChanged(int position, int charsRemoved, int charsAdded) + + This function is called whenever the contents of the document change. A + change occurs when text is inserted, removed, or a combination of these + two. The change is specified by \a position, \a charsRemoved, and + \a charsAdded corresponding to the starting character position of the + change, the number of characters removed from the document, and the + number of characters added. + + For example, when inserting the text "Hello" into an empty document, + \a charsRemoved would be 0 and \a charsAdded would be 5 (the length of + the string). + + Replacing text is a combination of removing and inserting. For example, if + the text "Hello" gets replaced by "Hi", \a charsRemoved would be 5 and + \a charsAdded would be 2. + + For subclasses of QAbstractTextDocumentLayout, this is the central function + where a large portion of the work to lay out and position document contents + is done. + + For example, in a subclass that only arranges blocks of text, an + implementation of this function would have to do the following: + + \list + \o Determine the list of changed \l{QTextBlock}(s) using the parameters + provided. + \o Each QTextBlock object's corresponding QTextLayout object needs to + be processed. You can access the \l{QTextBlock}'s layout using the + QTextBlock::layout() function. This processing should take the + document's page size into consideration. + \o If the total number of pages changed, the pageCountChanged() signal + should be emitted. + \o If the total size changed, the documentSizeChanged() signal should + be emitted. + \o The update() signal should be emitted to schedule a repaint of areas + in the layout that require repainting. + \endlist + + \sa QTextLayout +*/ + +/*! + \class QAbstractTextDocumentLayout::PaintContext + \reentrant + + \brief The QAbstractTextDocumentLayout::PaintContext class is a convenience + class defining the parameters used when painting a document's layout. + + A paint context is used when rendering custom layouts for QTextDocuments + with the QAbstractTextDocumentLayout::draw() function. It is specified by + a \l {cursorPosition}{cursor position}, \l {palette}{default text color}, + \l clip rectangle and a collection of \l selections. + + \sa QAbstractTextDocumentLayout +*/ + +/*! + \fn QAbstractTextDocumentLayout::PaintContext::PaintContext() + \internal +*/ + +/*! + \variable QAbstractTextDocumentLayout::PaintContext::cursorPosition + + \brief the position within the document, where the cursor line should be + drawn. + + The default value is -1. +*/ + +/*! + \variable QAbstractTextDocumentLayout::PaintContext::palette + + \brief the default color that is used for the text, when no color is + specified. + + The default value is the application's default palette. +*/ + +/*! + \variable QAbstractTextDocumentLayout::PaintContext::clip + + \brief a hint to the layout specifying the area around paragraphs, frames + or text require painting. + + Everything outside of this rectangle does not need to be painted. + + Specifying a clip rectangle can speed up drawing of large documents + significantly. Note that the clip rectangle is in document coordinates (not + in viewport coordinates). It is not a substitute for a clip region set on + the painter but merely a hint. + + The default value is a null rectangle indicating everything needs to be + painted. +*/ + +/*! + \variable QAbstractTextDocumentLayout::PaintContext::selections + + \brief the collection of selections that will be rendered when passing this + paint context to QAbstractTextDocumentLayout's draw() function. + + The default value is an empty vector indicating no selection. +*/ + +/*! + \class QAbstractTextDocumentLayout::Selection + \reentrant + + \brief The QAbstractTextDocumentLayout::Selection class is a convenience + class defining the parameters of a selection. + + A selection can be used to specify a part of a document that should be + highlighted when drawing custom layouts for QTextDocuments with the + QAbstractTextDocumentLayout::draw() function. It is specified using + \l cursor and a \l format. + + \sa QAbstractTextDocumentLayout, PaintContext +*/ + +/*! + \variable QAbstractTextDocumentLayout::Selection::format + + \brief the format of the selection + + The default value is QTextFormat::InvalidFormat. +*/ + +/*! + \variable QAbstractTextDocumentLayout::Selection::cursor + \brief the selection's cursor + + The default value is a null cursor. +*/ + +/*! + Creates a new text document layout for the given \a document. +*/ +QAbstractTextDocumentLayout::QAbstractTextDocumentLayout(QTextDocument *document) + : QObject(*new QAbstractTextDocumentLayoutPrivate, document) +{ + Q_D(QAbstractTextDocumentLayout); + d->setDocument(document); +} + +/*! + \internal +*/ +QAbstractTextDocumentLayout::QAbstractTextDocumentLayout(QAbstractTextDocumentLayoutPrivate &p, QTextDocument *document) + :QObject(p, document) +{ + Q_D(QAbstractTextDocumentLayout); + d->setDocument(document); +} + +/*! + \internal +*/ +QAbstractTextDocumentLayout::~QAbstractTextDocumentLayout() +{ +} + +/*! + \fn void QAbstractTextDocumentLayout::registerHandler(int objectType, QObject *component) + + Registers the given \a component as a handler for items of the given \a objectType. + + \note registerHandler() has to be called once for each object type. This + means that there is only one handler for multiple replacement characters + of the same object type. +*/ +void QAbstractTextDocumentLayout::registerHandler(int formatType, QObject *component) +{ + Q_D(QAbstractTextDocumentLayout); + + QTextObjectInterface *iface = qobject_cast(component); + if (!iface) + return; // ### print error message on terminal? + + connect(component, SIGNAL(destroyed(QObject*)), this, SLOT(_q_handlerDestroyed(QObject*))); + + QTextObjectHandler h; + h.iface = iface; + h.component = component; + d->handlers.insert(formatType, h); +} + +/*! + Returns a handler for objects of the given \a objectType. +*/ +QTextObjectInterface *QAbstractTextDocumentLayout::handlerForObject(int objectType) const +{ + Q_D(const QAbstractTextDocumentLayout); + + QTextObjectHandler handler = d->handlers.value(objectType); + if (!handler.component) + return 0; + + return handler.iface; +} + +/*! + Sets the size of the inline object \a item corresponding to the text + \a format. + + \a posInDocument specifies the position of the object within the document. + + The default implementation resizes the \a item to the size returned by + the object handler's intrinsicSize() function. This function is called only + within Qt. Subclasses can reimplement this function to customize the + resizing of inline objects. +*/ +void QAbstractTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format) +{ + Q_D(QAbstractTextDocumentLayout); + + QTextCharFormat f = format.toCharFormat(); + Q_ASSERT(f.isValid()); + QTextObjectHandler handler = d->handlers.value(f.objectType()); + if (!handler.component) + return; + + QSizeF s = handler.iface->intrinsicSize(document(), posInDocument, format); + item.setWidth(s.width()); + item.setAscent(s.height() - 1); + item.setDescent(0); +} + +/*! + Lays out the inline object \a item using the given text \a format. + + \a posInDocument specifies the position of the object within the document. + + The default implementation does nothing. This function is called only + within Qt. Subclasses can reimplement this function to customize the + position of inline objects. + + \sa drawInlineObject() +*/ +void QAbstractTextDocumentLayout::positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format) +{ + Q_UNUSED(item); + Q_UNUSED(posInDocument); + Q_UNUSED(format); +} + +/*! + \fn void QAbstractTextDocumentLayout::drawInlineObject(QPainter *painter, const QRectF &rect, QTextInlineObject object, int posInDocument, const QTextFormat &format) + + This function is called to draw the inline object, \a object, with the + given \a painter within the rectangle specified by \a rect using the + specified text \a format. + + \a posInDocument specifies the position of the object within the document. + + The default implementation calls drawObject() on the object handlers. This + function is called only within Qt. Subclasses can reimplement this function + to customize the drawing of inline objects. + + \sa draw() +*/ +void QAbstractTextDocumentLayout::drawInlineObject(QPainter *p, const QRectF &rect, QTextInlineObject item, + int posInDocument, const QTextFormat &format) +{ + Q_UNUSED(item); + Q_D(QAbstractTextDocumentLayout); + + QTextCharFormat f = format.toCharFormat(); + Q_ASSERT(f.isValid()); + QTextObjectHandler handler = d->handlers.value(f.objectType()); + if (!handler.component) + return; + + handler.iface->drawObject(p, rect, document(), posInDocument, format); +} + +void QAbstractTextDocumentLayoutPrivate::_q_handlerDestroyed(QObject *obj) +{ + HandlerHash::Iterator it = handlers.begin(); + while (it != handlers.end()) + if ((*it).component == obj) + it = handlers.erase(it); + else + ++it; +} + +/*! + \internal + + Returns the index of the format at position \a pos. +*/ +int QAbstractTextDocumentLayout::formatIndex(int pos) +{ + QTextDocumentPrivate *pieceTable = qobject_cast(parent())->docHandle(); + return pieceTable->find(pos).value()->format; +} + +/*! + \fn QTextCharFormat QAbstractTextDocumentLayout::format(int position) + + Returns the character format that is applicable at the given \a position. +*/ +QTextCharFormat QAbstractTextDocumentLayout::format(int pos) +{ + QTextDocumentPrivate *pieceTable = qobject_cast(parent())->docHandle(); + int idx = pieceTable->find(pos).value()->format; + return pieceTable->formatCollection()->charFormat(idx); +} + + + +/*! + Returns the text document that this layout is operating on. +*/ +QTextDocument *QAbstractTextDocumentLayout::document() const +{ + Q_D(const QAbstractTextDocumentLayout); + return d->document; +} + +/*! + \fn QString QAbstractTextDocumentLayout::anchorAt(const QPointF &position) const + + Returns the reference of the anchor the given \a position, or an empty + string if no anchor exists at that point. +*/ +QString QAbstractTextDocumentLayout::anchorAt(const QPointF& pos) const +{ + int cursorPos = hitTest(pos, Qt::ExactHit); + if (cursorPos == -1) + return QString(); + + QTextDocumentPrivate *pieceTable = qobject_cast(parent())->docHandle(); + QTextDocumentPrivate::FragmentIterator it = pieceTable->find(cursorPos); + QTextCharFormat fmt = pieceTable->formatCollection()->charFormat(it->format); + return fmt.anchorHref(); +} + +/*! + \fn QRectF QAbstractTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const + + Returns the bounding rectangle of \a frame. +*/ + +/*! + \fn QRectF QAbstractTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const + + Returns the bounding rectangle of \a block. +*/ + +/*! + Sets the paint device used for rendering the document's layout to the given + \a device. + + \sa paintDevice() +*/ +void QAbstractTextDocumentLayout::setPaintDevice(QPaintDevice *device) +{ + Q_D(QAbstractTextDocumentLayout); + d->paintDevice = device; +} + +/*! + Returns the paint device used to render the document's layout. + + \sa setPaintDevice() +*/ +QPaintDevice *QAbstractTextDocumentLayout::paintDevice() const +{ + Q_D(const QAbstractTextDocumentLayout); + return d->paintDevice; +} + +QT_END_NAMESPACE + +#include "moc_qabstracttextdocumentlayout.cpp" diff --git a/src/gui/text/qabstracttextdocumentlayout.h b/src/gui/text/qabstracttextdocumentlayout.h new file mode 100644 index 0000000000..a3cd27cb31 --- /dev/null +++ b/src/gui/text/qabstracttextdocumentlayout.h @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTTEXTDOCUMENTLAYOUT_H +#define QABSTRACTTEXTDOCUMENTLAYOUT_H + +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QAbstractTextDocumentLayoutPrivate; +class QTextBlock; +class QTextObjectInterface; +class QTextFrame; + +class Q_GUI_EXPORT QAbstractTextDocumentLayout : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QAbstractTextDocumentLayout) + +public: + explicit QAbstractTextDocumentLayout(QTextDocument *doc); + ~QAbstractTextDocumentLayout(); + + struct Selection + { + QTextCursor cursor; + QTextCharFormat format; + }; + + struct PaintContext + { + PaintContext() + : cursorPosition(-1) + {} + int cursorPosition; + QPalette palette; + QRectF clip; + QVector selections; + }; + + virtual void draw(QPainter *painter, const PaintContext &context) = 0; + virtual int hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const = 0; + QString anchorAt(const QPointF& pos) const; + + virtual int pageCount() const = 0; + virtual QSizeF documentSize() const = 0; + + virtual QRectF frameBoundingRect(QTextFrame *frame) const = 0; + virtual QRectF blockBoundingRect(const QTextBlock &block) const = 0; + + void setPaintDevice(QPaintDevice *device); + QPaintDevice *paintDevice() const; + + QTextDocument *document() const; + + void registerHandler(int objectType, QObject *component); + QTextObjectInterface *handlerForObject(int objectType) const; + +Q_SIGNALS: + void update(const QRectF & = QRectF(0., 0., 1000000000., 1000000000.)); + void updateBlock(const QTextBlock &block); + void documentSizeChanged(const QSizeF &newSize); + void pageCountChanged(int newPages); + +protected: + QAbstractTextDocumentLayout(QAbstractTextDocumentLayoutPrivate &, QTextDocument *); + + virtual void documentChanged(int from, int charsRemoved, int charsAdded) = 0; + + virtual void resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format); + virtual void positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format); + virtual void drawInlineObject(QPainter *painter, const QRectF &rect, QTextInlineObject object, int posInDocument, const QTextFormat &format); + + int formatIndex(int pos); + QTextCharFormat format(int pos); + +private: + friend class QTextControl; + friend class QTextDocument; + friend class QTextDocumentPrivate; + friend class QTextEngine; + friend class QTextLayout; + friend class QTextLine; + Q_PRIVATE_SLOT(d_func(), void _q_handlerDestroyed(QObject *obj)) + Q_PRIVATE_SLOT(d_func(), int _q_dynamicPageCountSlot()) + Q_PRIVATE_SLOT(d_func(), QSizeF _q_dynamicDocumentSizeSlot()) +}; + +class Q_GUI_EXPORT QTextObjectInterface +{ +public: + virtual ~QTextObjectInterface() {} + virtual QSizeF intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) = 0; + virtual void drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format) = 0; +}; + +Q_DECLARE_INTERFACE(QTextObjectInterface, "com.trolltech.Qt.QTextObjectInterface") + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTTEXTDOCUMENTLAYOUT_H diff --git a/src/gui/text/qabstracttextdocumentlayout_p.h b/src/gui/text/qabstracttextdocumentlayout_p.h new file mode 100644 index 0000000000..e1f236d4ee --- /dev/null +++ b/src/gui/text/qabstracttextdocumentlayout_p.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTTEXTDOCUMENTLAYOUT_P_H +#define QABSTRACTTEXTDOCUMENTLAYOUT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qobject_p.h" +#include "QtCore/qhash.h" + +QT_BEGIN_NAMESPACE + +struct QTextObjectHandler +{ + QTextObjectHandler() : iface(0) {} + QTextObjectInterface *iface; + QPointer component; +}; +typedef QHash HandlerHash; + +class QAbstractTextDocumentLayoutPrivate : public QObjectPrivate +{ +public: + Q_DECLARE_PUBLIC(QAbstractTextDocumentLayout) + + inline QAbstractTextDocumentLayoutPrivate() + : paintDevice(0) {} + + inline void setDocument(QTextDocument *doc) { + document = doc; + docPrivate = 0; + if (doc) + docPrivate = doc->docHandle(); + } + + inline int _q_dynamicPageCountSlot() const + { return q_func()->pageCount(); } + inline QSizeF _q_dynamicDocumentSizeSlot() const + { return q_func()->documentSize(); } + + HandlerHash handlers; + + void _q_handlerDestroyed(QObject *obj); + QPaintDevice *paintDevice; + + QTextDocument *document; + QTextDocumentPrivate *docPrivate; +}; + +QT_END_NAMESPACE + +#endif // QABSTRACTTEXTDOCUMENTLAYOUT_P_H diff --git a/src/gui/text/qcssparser.cpp b/src/gui/text/qcssparser.cpp new file mode 100644 index 0000000000..052dc72b63 --- /dev/null +++ b/src/gui/text/qcssparser.cpp @@ -0,0 +1,2768 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcssparser_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include "private/qfunctions_p.h" + +#ifndef QT_NO_CSSPARSER + +QT_BEGIN_NAMESPACE + +#include "qcssscanner.cpp" + +using namespace QCss; + +struct QCssKnownValue +{ + const char *name; + quint64 id; +}; + +static const QCssKnownValue properties[NumProperties - 1] = { + { "-qt-background-role", QtBackgroundRole }, + { "-qt-block-indent", QtBlockIndent }, + { "-qt-list-indent", QtListIndent }, + { "-qt-list-number-prefix", QtListNumberPrefix }, + { "-qt-list-number-suffix", QtListNumberSuffix }, + { "-qt-paragraph-type", QtParagraphType }, + { "-qt-style-features", QtStyleFeatures }, + { "-qt-table-type", QtTableType }, + { "-qt-user-state", QtUserState }, + { "alternate-background-color", QtAlternateBackground }, + { "background", Background }, + { "background-attachment", BackgroundAttachment }, + { "background-clip", BackgroundClip }, + { "background-color", BackgroundColor }, + { "background-image", BackgroundImage }, + { "background-origin", BackgroundOrigin }, + { "background-position", BackgroundPosition }, + { "background-repeat", BackgroundRepeat }, + { "border", Border }, + { "border-bottom", BorderBottom }, + { "border-bottom-color", BorderBottomColor }, + { "border-bottom-left-radius", BorderBottomLeftRadius }, + { "border-bottom-right-radius", BorderBottomRightRadius }, + { "border-bottom-style", BorderBottomStyle }, + { "border-bottom-width", BorderBottomWidth }, + { "border-color", BorderColor }, + { "border-image", BorderImage }, + { "border-left", BorderLeft }, + { "border-left-color", BorderLeftColor }, + { "border-left-style", BorderLeftStyle }, + { "border-left-width", BorderLeftWidth }, + { "border-radius", BorderRadius }, + { "border-right", BorderRight }, + { "border-right-color", BorderRightColor }, + { "border-right-style", BorderRightStyle }, + { "border-right-width", BorderRightWidth }, + { "border-style", BorderStyles }, + { "border-top", BorderTop }, + { "border-top-color", BorderTopColor }, + { "border-top-left-radius", BorderTopLeftRadius }, + { "border-top-right-radius", BorderTopRightRadius }, + { "border-top-style", BorderTopStyle }, + { "border-top-width", BorderTopWidth }, + { "border-width", BorderWidth }, + { "bottom", Bottom }, + { "color", Color }, + { "float", Float }, + { "font", Font }, + { "font-family", FontFamily }, + { "font-size", FontSize }, + { "font-style", FontStyle }, + { "font-variant", FontVariant }, + { "font-weight", FontWeight }, + { "height", Height }, + { "image", QtImage }, + { "image-position", QtImageAlignment }, + { "left", Left }, + { "line-height", LineHeight }, + { "list-style", ListStyle }, + { "list-style-type", ListStyleType }, + { "margin" , Margin }, + { "margin-bottom", MarginBottom }, + { "margin-left", MarginLeft }, + { "margin-right", MarginRight }, + { "margin-top", MarginTop }, + { "max-height", MaximumHeight }, + { "max-width", MaximumWidth }, + { "min-height", MinimumHeight }, + { "min-width", MinimumWidth }, + { "outline", Outline }, + { "outline-bottom-left-radius", OutlineBottomLeftRadius }, + { "outline-bottom-right-radius", OutlineBottomRightRadius }, + { "outline-color", OutlineColor }, + { "outline-offset", OutlineOffset }, + { "outline-radius", OutlineRadius }, + { "outline-style", OutlineStyle }, + { "outline-top-left-radius", OutlineTopLeftRadius }, + { "outline-top-right-radius", OutlineTopRightRadius }, + { "outline-width", OutlineWidth }, + { "padding", Padding }, + { "padding-bottom", PaddingBottom }, + { "padding-left", PaddingLeft }, + { "padding-right", PaddingRight }, + { "padding-top", PaddingTop }, + { "page-break-after", PageBreakAfter }, + { "page-break-before", PageBreakBefore }, + { "position", Position }, + { "right", Right }, + { "selection-background-color", QtSelectionBackground }, + { "selection-color", QtSelectionForeground }, + { "spacing", QtSpacing }, + { "subcontrol-origin", QtOrigin }, + { "subcontrol-position", QtPosition }, + { "text-align", TextAlignment }, + { "text-decoration", TextDecoration }, + { "text-indent", TextIndent }, + { "text-transform", TextTransform }, + { "text-underline-style", TextUnderlineStyle }, + { "top", Top }, + { "vertical-align", VerticalAlignment }, + { "white-space", Whitespace }, + { "width", Width } +}; + +static const QCssKnownValue values[NumKnownValues - 1] = { + { "active", Value_Active }, + { "alternate-base", Value_AlternateBase }, + { "always", Value_Always }, + { "auto", Value_Auto }, + { "base", Value_Base }, + { "bold", Value_Bold }, + { "bottom", Value_Bottom }, + { "bright-text", Value_BrightText }, + { "button", Value_Button }, + { "button-text", Value_ButtonText }, + { "center", Value_Center }, + { "circle", Value_Circle }, + { "dark", Value_Dark }, + { "dashed", Value_Dashed }, + { "decimal", Value_Decimal }, + { "disabled", Value_Disabled }, + { "disc", Value_Disc }, + { "dot-dash", Value_DotDash }, + { "dot-dot-dash", Value_DotDotDash }, + { "dotted", Value_Dotted }, + { "double", Value_Double }, + { "groove", Value_Groove }, + { "highlight", Value_Highlight }, + { "highlighted-text", Value_HighlightedText }, + { "inset", Value_Inset }, + { "italic", Value_Italic }, + { "large", Value_Large }, + { "left", Value_Left }, + { "light", Value_Light }, + { "line-through", Value_LineThrough }, + { "link", Value_Link }, + { "link-visited", Value_LinkVisited }, + { "lower-alpha", Value_LowerAlpha }, + { "lower-roman", Value_LowerRoman }, + { "lowercase", Value_Lowercase }, + { "medium", Value_Medium }, + { "mid", Value_Mid }, + { "middle", Value_Middle }, + { "midlight", Value_Midlight }, + { "native", Value_Native }, + { "none", Value_None }, + { "normal", Value_Normal }, + { "nowrap", Value_NoWrap }, + { "oblique", Value_Oblique }, + { "off", Value_Off }, + { "on", Value_On }, + { "outset", Value_Outset }, + { "overline", Value_Overline }, + { "pre", Value_Pre }, + { "pre-wrap", Value_PreWrap }, + { "ridge", Value_Ridge }, + { "right", Value_Right }, + { "selected", Value_Selected }, + { "shadow", Value_Shadow }, + { "small" , Value_Small }, + { "small-caps", Value_SmallCaps }, + { "solid", Value_Solid }, + { "square", Value_Square }, + { "sub", Value_Sub }, + { "super", Value_Super }, + { "text", Value_Text }, + { "top", Value_Top }, + { "transparent", Value_Transparent }, + { "underline", Value_Underline }, + { "upper-alpha", Value_UpperAlpha }, + { "upper-roman", Value_UpperRoman }, + { "uppercase", Value_Uppercase }, + { "wave", Value_Wave }, + { "window", Value_Window }, + { "window-text", Value_WindowText }, + { "x-large", Value_XLarge }, + { "xx-large", Value_XXLarge } +}; + +//Map id to strings as they appears in the 'values' array above +static const short indexOfId[NumKnownValues] = { 0, 41, 48, 42, 49, 54, 35, 26, 70, 71, 25, 43, 5, 63, 47, + 29, 58, 59, 27, 51, 61, 6, 10, 39, 56, 19, 13, 17, 18, 20, 21, 50, 24, 46, 67, 37, 3, 2, 40, 62, 16, + 11, 57, 14, 32, 64, 33, 65, 55, 66, 34, 69, 8, 28, 38, 12, 36, 60, 7, 9, 4, 68, 53, 22, 23, 30, 31, + 1, 15, 0, 52, 45, 44 }; + +QString Value::toString() const +{ + if (type == KnownIdentifier) { + return QLatin1String(values[indexOfId[variant.toInt()]].name); + } else { + return variant.toString(); + } +} + +static const QCssKnownValue pseudos[NumPseudos - 1] = { + { "active", PseudoClass_Active }, + { "adjoins-item", PseudoClass_Item }, + { "alternate", PseudoClass_Alternate }, + { "bottom", PseudoClass_Bottom }, + { "checked", PseudoClass_Checked }, + { "closable", PseudoClass_Closable }, + { "closed", PseudoClass_Closed }, + { "default", PseudoClass_Default }, + { "disabled", PseudoClass_Disabled }, + { "edit-focus", PseudoClass_EditFocus }, + { "editable", PseudoClass_Editable }, + { "enabled", PseudoClass_Enabled }, + { "exclusive", PseudoClass_Exclusive }, + { "first", PseudoClass_First }, + { "flat", PseudoClass_Flat }, + { "floatable", PseudoClass_Floatable }, + { "focus", PseudoClass_Focus }, + { "has-children", PseudoClass_Children }, + { "has-siblings", PseudoClass_Sibling }, + { "horizontal", PseudoClass_Horizontal }, + { "hover", PseudoClass_Hover }, + { "indeterminate" , PseudoClass_Indeterminate }, + { "last", PseudoClass_Last }, + { "left", PseudoClass_Left }, + { "maximized", PseudoClass_Maximized }, + { "middle", PseudoClass_Middle }, + { "minimized", PseudoClass_Minimized }, + { "movable", PseudoClass_Movable }, + { "next-selected", PseudoClass_NextSelected }, + { "no-frame", PseudoClass_Frameless }, + { "non-exclusive", PseudoClass_NonExclusive }, + { "off", PseudoClass_Unchecked }, + { "on", PseudoClass_Checked }, + { "only-one", PseudoClass_OnlyOne }, + { "open", PseudoClass_Open }, + { "pressed", PseudoClass_Pressed }, + { "previous-selected", PseudoClass_PreviousSelected }, + { "read-only", PseudoClass_ReadOnly }, + { "right", PseudoClass_Right }, + { "selected", PseudoClass_Selected }, + { "top", PseudoClass_Top }, + { "unchecked" , PseudoClass_Unchecked }, + { "vertical", PseudoClass_Vertical }, + { "window", PseudoClass_Window } +}; + +static const QCssKnownValue origins[NumKnownOrigins - 1] = { + { "border", Origin_Border }, + { "content", Origin_Content }, + { "margin", Origin_Margin }, // not in css + { "padding", Origin_Padding } +}; + +static const QCssKnownValue repeats[NumKnownRepeats - 1] = { + { "no-repeat", Repeat_None }, + { "repeat-x", Repeat_X }, + { "repeat-xy", Repeat_XY }, + { "repeat-y", Repeat_Y } +}; + +static const QCssKnownValue tileModes[NumKnownTileModes - 1] = { + { "repeat", TileMode_Repeat }, + { "round", TileMode_Round }, + { "stretch", TileMode_Stretch }, +}; + +static const QCssKnownValue positions[NumKnownPositionModes - 1] = { + { "absolute", PositionMode_Absolute }, + { "fixed", PositionMode_Fixed }, + { "relative", PositionMode_Relative }, + { "static", PositionMode_Static } +}; + +static const QCssKnownValue attachments[NumKnownAttachments - 1] = { + { "fixed", Attachment_Fixed }, + { "scroll", Attachment_Scroll } +}; + +static const QCssKnownValue styleFeatures[NumKnownStyleFeatures - 1] = { + { "background-color", StyleFeature_BackgroundColor }, + { "background-gradient", StyleFeature_BackgroundGradient }, + { "none", StyleFeature_None } +}; + +Q_STATIC_GLOBAL_OPERATOR bool operator<(const QString &name, const QCssKnownValue &prop) +{ + return QString::compare(name, QLatin1String(prop.name), Qt::CaseInsensitive) < 0; +} + +Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCssKnownValue &prop, const QString &name) +{ + return QString::compare(QLatin1String(prop.name), name, Qt::CaseInsensitive) < 0; +} + +static quint64 findKnownValue(const QString &name, const QCssKnownValue *start, int numValues) +{ + const QCssKnownValue *end = &start[numValues - 1]; + const QCssKnownValue *prop = qBinaryFind(start, end, name); + if (prop == end) + return 0; + return prop->id; +} + +/////////////////////////////////////////////////////////////////////////////// +// Value Extractor +ValueExtractor::ValueExtractor(const QVector &decls, const QPalette &pal) +: declarations(decls), adjustment(0), fontExtracted(false), pal(pal) +{ +} + +LengthData ValueExtractor::lengthValue(const Value& v) +{ + QString s = v.variant.toString(); + s.reserve(s.length()); + LengthData data; + data.unit = LengthData::None; + if (s.endsWith(QLatin1String("px"), Qt::CaseInsensitive)) + data.unit = LengthData::Px; + else if (s.endsWith(QLatin1String("ex"), Qt::CaseInsensitive)) + data.unit = LengthData::Ex; + else if (s.endsWith(QLatin1String("em"), Qt::CaseInsensitive)) + data.unit = LengthData::Em; + + if (data.unit != LengthData::None) + s.chop(2); + + data.number = s.toDouble(); + return data; +} + +static int lengthValueFromData(const LengthData& data, const QFont& f) +{ + if (data.unit == LengthData::Ex) + return qRound(QFontMetrics(f).xHeight() * data.number); + else if (data.unit == LengthData::Em) + return qRound(QFontMetrics(f).height() * data.number); + return qRound(data.number); +} + +int ValueExtractor::lengthValue(const Declaration &decl) +{ + if (decl.d->parsed.isValid()) + return lengthValueFromData(qvariant_cast(decl.d->parsed), f); + if (decl.d->values.count() < 1) + return 0; + LengthData data = lengthValue(decl.d->values.at(0)); + decl.d->parsed = QVariant::fromValue(data); + return lengthValueFromData(data,f); +} + +void ValueExtractor::lengthValues(const Declaration &decl, int *m) +{ + if (decl.d->parsed.isValid()) { + QList v = decl.d->parsed.toList(); + for (int i = 0; i < 4; i++) + m[i] = lengthValueFromData(qvariant_cast(v.at(i)), f); + return; + } + + LengthData datas[4]; + int i; + for (i = 0; i < qMin(decl.d->values.count(), 4); i++) + datas[i] = lengthValue(decl.d->values[i]); + + if (i == 0) { + LengthData zero = {0.0, LengthData::None}; + datas[0] = datas[1] = datas[2] = datas[3] = zero; + } else if (i == 1) { + datas[3] = datas[2] = datas[1] = datas[0]; + } else if (i == 2) { + datas[2] = datas[0]; + datas[3] = datas[1]; + } else if (i == 3) { + datas[3] = datas[1]; + } + + QList v; + for (i = 0; i < 4; i++) { + v += QVariant::fromValue(datas[i]); + m[i] = lengthValueFromData(datas[i], f); + } + decl.d->parsed = v; +} + +bool ValueExtractor::extractGeometry(int *w, int *h, int *minw, int *minh, int *maxw, int *maxh) +{ + extractFont(); + bool hit = false; + for (int i = 0; i < declarations.count(); i++) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case Width: *w = lengthValue(decl); break; + case Height: *h = lengthValue(decl); break; + case MinimumWidth: *minw = lengthValue(decl); break; + case MinimumHeight: *minh = lengthValue(decl); break; + case MaximumWidth: *maxw = lengthValue(decl); break; + case MaximumHeight: *maxh = lengthValue(decl); break; + default: continue; + } + hit = true; + } + + return hit; +} + +bool ValueExtractor::extractPosition(int *left, int *top, int *right, int *bottom, QCss::Origin *origin, + Qt::Alignment *position, QCss::PositionMode *mode, Qt::Alignment *textAlignment) +{ + extractFont(); + bool hit = false; + for (int i = 0; i < declarations.count(); i++) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case Left: *left = lengthValue(decl); break; + case Top: *top = lengthValue(decl); break; + case Right: *right = lengthValue(decl); break; + case Bottom: *bottom = lengthValue(decl); break; + case QtOrigin: *origin = decl.originValue(); break; + case QtPosition: *position = decl.alignmentValue(); break; + case TextAlignment: *textAlignment = decl.alignmentValue(); break; + case Position: *mode = decl.positionValue(); break; + default: continue; + } + hit = true; + } + + return hit; +} + +bool ValueExtractor::extractBox(int *margins, int *paddings, int *spacing) +{ + extractFont(); + bool hit = false; + for (int i = 0; i < declarations.count(); i++) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case PaddingLeft: paddings[LeftEdge] = lengthValue(decl); break; + case PaddingRight: paddings[RightEdge] = lengthValue(decl); break; + case PaddingTop: paddings[TopEdge] = lengthValue(decl); break; + case PaddingBottom: paddings[BottomEdge] = lengthValue(decl); break; + case Padding: lengthValues(decl, paddings); break; + + case MarginLeft: margins[LeftEdge] = lengthValue(decl); break; + case MarginRight: margins[RightEdge] = lengthValue(decl); break; + case MarginTop: margins[TopEdge] = lengthValue(decl); break; + case MarginBottom: margins[BottomEdge] = lengthValue(decl); break; + case Margin: lengthValues(decl, margins); break; + case QtSpacing: if (spacing) *spacing = lengthValue(decl); break; + + default: continue; + } + hit = true; + } + + return hit; +} + +int ValueExtractor::extractStyleFeatures() +{ + int features = StyleFeature_None; + for (int i = 0; i < declarations.count(); i++) { + const Declaration &decl = declarations.at(i); + if (decl.d->propertyId == QtStyleFeatures) + features = decl.styleFeaturesValue(); + } + return features; +} + +QSize ValueExtractor::sizeValue(const Declaration &decl) +{ + if (decl.d->parsed.isValid()) { + QList v = decl.d->parsed.toList(); + return QSize(lengthValueFromData(qvariant_cast(v.at(0)), f), + lengthValueFromData(qvariant_cast(v.at(1)), f)); + } + + LengthData x[2] = { {0, LengthData::None }, {0, LengthData::None} }; + if (decl.d->values.count() > 0) + x[0] = lengthValue(decl.d->values.at(0)); + if (decl.d->values.count() > 1) + x[1] = lengthValue(decl.d->values.at(1)); + else + x[1] = x[0]; + QList v; + v << QVariant::fromValue(x[0]) << qVariantFromValue(x[1]); + decl.d->parsed = v; + return QSize(lengthValueFromData(x[0], f), lengthValueFromData(x[1], f)); +} + +void ValueExtractor::sizeValues(const Declaration &decl, QSize *radii) +{ + radii[0] = sizeValue(decl); + for (int i = 1; i < 4; i++) + radii[i] = radii[0]; +} + +bool ValueExtractor::extractBorder(int *borders, QBrush *colors, BorderStyle *styles, + QSize *radii) +{ + extractFont(); + bool hit = false; + for (int i = 0; i < declarations.count(); i++) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case BorderLeftWidth: borders[LeftEdge] = lengthValue(decl); break; + case BorderRightWidth: borders[RightEdge] = lengthValue(decl); break; + case BorderTopWidth: borders[TopEdge] = lengthValue(decl); break; + case BorderBottomWidth: borders[BottomEdge] = lengthValue(decl); break; + case BorderWidth: lengthValues(decl, borders); break; + + case BorderLeftColor: colors[LeftEdge] = decl.brushValue(pal); break; + case BorderRightColor: colors[RightEdge] = decl.brushValue(pal); break; + case BorderTopColor: colors[TopEdge] = decl.brushValue(pal); break; + case BorderBottomColor: colors[BottomEdge] = decl.brushValue(pal); break; + case BorderColor: decl.brushValues(colors, pal); break; + + case BorderTopStyle: styles[TopEdge] = decl.styleValue(); break; + case BorderBottomStyle: styles[BottomEdge] = decl.styleValue(); break; + case BorderLeftStyle: styles[LeftEdge] = decl.styleValue(); break; + case BorderRightStyle: styles[RightEdge] = decl.styleValue(); break; + case BorderStyles: decl.styleValues(styles); break; + + case BorderTopLeftRadius: radii[0] = sizeValue(decl); break; + case BorderTopRightRadius: radii[1] = sizeValue(decl); break; + case BorderBottomLeftRadius: radii[2] = sizeValue(decl); break; + case BorderBottomRightRadius: radii[3] = sizeValue(decl); break; + case BorderRadius: sizeValues(decl, radii); break; + + case BorderLeft: + borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]); + break; + case BorderTop: + borderValue(decl, &borders[TopEdge], &styles[TopEdge], &colors[TopEdge]); + break; + case BorderRight: + borderValue(decl, &borders[RightEdge], &styles[RightEdge], &colors[RightEdge]); + break; + case BorderBottom: + borderValue(decl, &borders[BottomEdge], &styles[BottomEdge], &colors[BottomEdge]); + break; + case Border: + borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]); + borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge]; + styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge]; + colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge]; + break; + + default: continue; + } + hit = true; + } + + return hit; +} + +bool ValueExtractor::extractOutline(int *borders, QBrush *colors, BorderStyle *styles, + QSize *radii, int *offsets) +{ + extractFont(); + bool hit = false; + for (int i = 0; i < declarations.count(); i++) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case OutlineWidth: lengthValues(decl, borders); break; + case OutlineColor: decl.brushValues(colors, pal); break; + case OutlineStyle: decl.styleValues(styles); break; + + case OutlineTopLeftRadius: radii[0] = sizeValue(decl); break; + case OutlineTopRightRadius: radii[1] = sizeValue(decl); break; + case OutlineBottomLeftRadius: radii[2] = sizeValue(decl); break; + case OutlineBottomRightRadius: radii[3] = sizeValue(decl); break; + case OutlineRadius: sizeValues(decl, radii); break; + case OutlineOffset: lengthValues(decl, offsets); break; + + case Outline: + borderValue(decl, &borders[LeftEdge], &styles[LeftEdge], &colors[LeftEdge]); + borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge]; + styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge]; + colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge]; + break; + + default: continue; + } + hit = true; + } + + return hit; +} + +static Qt::Alignment parseAlignment(const QCss::Value *values, int count) +{ + Qt::Alignment a[2] = { 0, 0 }; + for (int i = 0; i < qMin(2, count); i++) { + if (values[i].type != Value::KnownIdentifier) + break; + switch (values[i].variant.toInt()) { + case Value_Left: a[i] = Qt::AlignLeft; break; + case Value_Right: a[i] = Qt::AlignRight; break; + case Value_Top: a[i] = Qt::AlignTop; break; + case Value_Bottom: a[i] = Qt::AlignBottom; break; + case Value_Center: a[i] = Qt::AlignCenter; break; + default: break; + } + } + + if (a[0] == Qt::AlignCenter && a[1] != 0 && a[1] != Qt::AlignCenter) + a[0] = (a[1] == Qt::AlignLeft || a[1] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter; + if ((a[1] == 0 || a[1] == Qt::AlignCenter) && a[0] != Qt::AlignCenter) + a[1] = (a[0] == Qt::AlignLeft || a[0] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter; + return a[0] | a[1]; +} + +static ColorData parseColorValue(QCss::Value v) +{ + if (v.type == Value::Identifier || v.type == Value::String) { + v.variant.convert(QVariant::Color); + v.type = Value::Color; + } + + if (v.type == Value::Color) + return qvariant_cast(v.variant); + + if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent) + return QColor(Qt::transparent); + + if (v.type != Value::Function) + return ColorData(); + + QStringList lst = v.variant.toStringList(); + if (lst.count() != 2) + return ColorData(); + + if ((lst.at(0).compare(QLatin1String("palette"), Qt::CaseInsensitive)) == 0) { + int role = findKnownValue(lst.at(1).trimmed(), values, NumKnownValues); + if (role >= Value_FirstColorRole && role <= Value_LastColorRole) + return (QPalette::ColorRole)(role-Value_FirstColorRole); + + return ColorData(); + } + + bool rgb = lst.at(0).startsWith(QLatin1String("rgb")); + + Parser p(lst.at(1)); + if (!p.testExpr()) + return ColorData(); + + QVector colorDigits; + if (!p.parseExpr(&colorDigits)) + return ColorData(); + + for (int i = 0; i < qMin(colorDigits.count(), 7); i += 2) { + if (colorDigits.at(i).type == Value::Percentage) { + colorDigits[i].variant = colorDigits.at(i).variant.toReal() * (255. / 100.); + colorDigits[i].type = Value::Number; + } else if (colorDigits.at(i).type != Value::Number) { + return ColorData(); + } + } + + int v1 = colorDigits.at(0).variant.toInt(); + int v2 = colorDigits.at(2).variant.toInt(); + int v3 = colorDigits.at(4).variant.toInt(); + int alpha = colorDigits.count() >= 7 ? colorDigits.at(6).variant.toInt() : 255; + + return rgb ? QColor::fromRgb(v1, v2, v3, alpha) + : QColor::fromHsv(v1, v2, v3, alpha); +} + +static QColor colorFromData(const ColorData& c, const QPalette &pal) +{ + if (c.type == ColorData::Color) { + return c.color; + } else if (c.type == ColorData::Role) { + return pal.color(c.role); + } + return QColor(); +} + +static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal) +{ + ColorData c = parseColorValue(v); + if (c.type == ColorData::Color) { + return QBrush(c.color); + } else if (c.type == ColorData::Role) { + return c.role; + } + + if (v.type != Value::Function) + return BrushData(); + + QStringList lst = v.variant.toStringList(); + if (lst.count() != 2) + return BrushData(); + + QStringList gradFuncs; + gradFuncs << QLatin1String("qlineargradient") << QLatin1String("qradialgradient") << QLatin1String("qconicalgradient") << QLatin1String("qgradient"); + int gradType = -1; + + if ((gradType = gradFuncs.indexOf(lst.at(0).toLower())) == -1) + return BrushData(); + + QHash vars; + QVector stops; + + int spread = -1; + QStringList spreads; + spreads << QLatin1String("pad") << QLatin1String("reflect") << QLatin1String("repeat"); + + bool dependsOnThePalette = false; + Parser parser(lst.at(1)); + while (parser.hasNext()) { + parser.skipSpace(); + if (!parser.test(IDENT)) + return BrushData(); + QString attr = parser.lexem(); + parser.skipSpace(); + if (!parser.test(COLON)) + return BrushData(); + parser.skipSpace(); + if (attr.compare(QLatin1String("stop"), Qt::CaseInsensitive) == 0) { + QCss::Value stop, color; + parser.next(); + if (!parser.parseTerm(&stop)) return BrushData(); + parser.skipSpace(); + parser.next(); + if (!parser.parseTerm(&color)) return BrushData(); + ColorData cd = parseColorValue(color); + if(cd.type == ColorData::Role) + dependsOnThePalette = true; + stops.append(QGradientStop(stop.variant.toReal(), colorFromData(cd, pal))); + } else { + parser.next(); + QCss::Value value; + (void)parser.parseTerm(&value); + if (attr.compare(QLatin1String("spread"), Qt::CaseInsensitive) == 0) { + spread = spreads.indexOf(value.variant.toString()); + } else { + vars[attr] = value.variant.toReal(); + } + } + parser.skipSpace(); + (void)parser.test(COMMA); + } + + if (gradType == 0) { + QLinearGradient lg(vars.value(QLatin1String("x1")), vars.value(QLatin1String("y1")), + vars.value(QLatin1String("x2")), vars.value(QLatin1String("y2"))); + lg.setCoordinateMode(QGradient::ObjectBoundingMode); + lg.setStops(stops); + if (spread != -1) + lg.setSpread(QGradient::Spread(spread)); + BrushData bd = QBrush(lg); + if (dependsOnThePalette) + bd.type = BrushData::DependsOnThePalette; + return bd; + } + + if (gradType == 1) { + QRadialGradient rg(vars.value(QLatin1String("cx")), vars.value(QLatin1String("cy")), + vars.value(QLatin1String("radius")), vars.value(QLatin1String("fx")), + vars.value(QLatin1String("fy"))); + rg.setCoordinateMode(QGradient::ObjectBoundingMode); + rg.setStops(stops); + if (spread != -1) + rg.setSpread(QGradient::Spread(spread)); + BrushData bd = QBrush(rg); + if (dependsOnThePalette) + bd.type = BrushData::DependsOnThePalette; + return bd; + } + + if (gradType == 2) { + QConicalGradient cg(vars.value(QLatin1String("cx")), vars.value(QLatin1String("cy")), + vars.value(QLatin1String("angle"))); + cg.setCoordinateMode(QGradient::ObjectBoundingMode); + cg.setStops(stops); + if (spread != -1) + cg.setSpread(QGradient::Spread(spread)); + BrushData bd = QBrush(cg); + if (dependsOnThePalette) + bd.type = BrushData::DependsOnThePalette; + return bd; + } + + return BrushData(); +} + +static QBrush brushFromData(const BrushData& c, const QPalette &pal) +{ + if (c.type == BrushData::Role) { + return pal.color(c.role); + } else { + return c.brush; + } +} + +static BorderStyle parseStyleValue(QCss::Value v) +{ + if (v.type == Value::KnownIdentifier) { + switch (v.variant.toInt()) { + case Value_None: + return BorderStyle_None; + case Value_Dotted: + return BorderStyle_Dotted; + case Value_Dashed: + return BorderStyle_Dashed; + case Value_Solid: + return BorderStyle_Solid; + case Value_Double: + return BorderStyle_Double; + case Value_DotDash: + return BorderStyle_DotDash; + case Value_DotDotDash: + return BorderStyle_DotDotDash; + case Value_Groove: + return BorderStyle_Groove; + case Value_Ridge: + return BorderStyle_Ridge; + case Value_Inset: + return BorderStyle_Inset; + case Value_Outset: + return BorderStyle_Outset; + case Value_Native: + return BorderStyle_Native; + default: + break; + } + } + + return BorderStyle_Unknown; +} + +void ValueExtractor::borderValue(const Declaration &decl, int *width, QCss::BorderStyle *style, QBrush *color) +{ + if (decl.d->parsed.isValid()) { + BorderData data = qvariant_cast(decl.d->parsed); + *width = lengthValueFromData(data.width, f); + *style = data.style; + *color = data.color.type != BrushData::Invalid ? brushFromData(data.color, pal) : QBrush(QColor()); + return; + } + + *width = 0; + *style = BorderStyle_None; + *color = QColor(); + + if (decl.d->values.isEmpty()) + return; + + BorderData data; + data.width.number = 0; + data.width.unit = LengthData::None; + data.style = BorderStyle_None; + + int i = 0; + if (decl.d->values.at(i).type == Value::Length || decl.d->values.at(i).type == Value::Number) { + data.width = lengthValue(decl.d->values.at(i)); + *width = lengthValueFromData(data.width, f); + if (++i >= decl.d->values.count()) { + decl.d->parsed = QVariant::fromValue(data); + return; + } + } + + data.style = parseStyleValue(decl.d->values.at(i)); + if (data.style != BorderStyle_Unknown) { + *style = data.style; + if (++i >= decl.d->values.count()) { + decl.d->parsed = QVariant::fromValue(data); + return; + } + } else { + data.style = BorderStyle_None; + } + + data.color = parseBrushValue(decl.d->values.at(i), pal); + *color = brushFromData(data.color, pal); + if (data.color.type != BrushData::DependsOnThePalette) + decl.d->parsed = QVariant::fromValue(data); +} + +static void parseShorthandBackgroundProperty(const QVector &values, BrushData *brush, QString *image, Repeat *repeat, Qt::Alignment *alignment, const QPalette &pal) +{ + *brush = BrushData(); + *image = QString(); + *repeat = Repeat_XY; + *alignment = Qt::AlignTop | Qt::AlignLeft; + + for (int i = 0; i < values.count(); ++i) { + const QCss::Value &v = values.at(i); + if (v.type == Value::Uri) { + *image = v.variant.toString(); + continue; + } else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_None) { + *image = QString(); + continue; + } else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent) { + *brush = QBrush(Qt::transparent); + } + + Repeat repeatAttempt = static_cast(findKnownValue(v.variant.toString(), + repeats, NumKnownRepeats)); + if (repeatAttempt != Repeat_Unknown) { + *repeat = repeatAttempt; + continue; + } + + if (v.type == Value::KnownIdentifier) { + const int start = i; + int count = 1; + if (i < values.count() - 1 + && values.at(i + 1).type == Value::KnownIdentifier) { + ++i; + ++count; + } + Qt::Alignment a = parseAlignment(values.constData() + start, count); + if (int(a) != 0) { + *alignment = a; + continue; + } + i -= count - 1; + } + + *brush = parseBrushValue(v, pal); + } +} + +bool ValueExtractor::extractBackground(QBrush *brush, QString *image, Repeat *repeat, + Qt::Alignment *alignment, Origin *origin, Attachment *attachment, + Origin *clip) +{ + bool hit = false; + for (int i = 0; i < declarations.count(); ++i) { + const Declaration &decl = declarations.at(i); + if (decl.d->values.isEmpty()) + continue; + const QCss::Value &val = decl.d->values.at(0); + switch (decl.d->propertyId) { + case BackgroundColor: + *brush = decl.brushValue(); + break; + case BackgroundImage: + if (val.type == Value::Uri) + *image = val.variant.toString(); + break; + case BackgroundRepeat: + if (decl.d->parsed.isValid()) { + *repeat = static_cast(decl.d->parsed.toInt()); + } else { + *repeat = static_cast(findKnownValue(val.variant.toString(), + repeats, NumKnownRepeats)); + decl.d->parsed = *repeat; + } + break; + case BackgroundPosition: + *alignment = decl.alignmentValue(); + break; + case BackgroundOrigin: + *origin = decl.originValue(); + break; + case BackgroundClip: + *clip = decl.originValue(); + break; + case Background: + if (decl.d->parsed.isValid()) { + BackgroundData data = qvariant_cast(decl.d->parsed); + *brush = brushFromData(data.brush, pal); + *image = data.image; + *repeat = data.repeat; + *alignment = data.alignment; + } else { + BrushData brushData; + parseShorthandBackgroundProperty(decl.d->values, &brushData, image, repeat, alignment, pal); + *brush = brushFromData(brushData, pal); + if (brushData.type != BrushData::DependsOnThePalette) { + BackgroundData data = { brushData, *image, *repeat, *alignment }; + decl.d->parsed = QVariant::fromValue(data); + } + } + break; + case BackgroundAttachment: + *attachment = decl.attachmentValue(); + break; + default: continue; + } + hit = true; + } + return hit; +} + +static bool setFontSizeFromValue(QCss::Value value, QFont *font, int *fontSizeAdjustment) +{ + if (value.type == Value::KnownIdentifier) { + bool valid = true; + switch (value.variant.toInt()) { + case Value_Small: *fontSizeAdjustment = -1; break; + case Value_Medium: *fontSizeAdjustment = 0; break; + case Value_Large: *fontSizeAdjustment = 1; break; + case Value_XLarge: *fontSizeAdjustment = 2; break; + case Value_XXLarge: *fontSizeAdjustment = 3; break; + default: valid = false; break; + } + return valid; + } + if (value.type != Value::Length) + return false; + + bool valid = false; + QString s = value.variant.toString(); + if (s.endsWith(QLatin1String("pt"), Qt::CaseInsensitive)) { + s.chop(2); + value.variant = s; + if (value.variant.convert((QVariant::Type)qMetaTypeId())) { + font->setPointSizeF(value.variant.toReal()); + valid = true; + } + } else if (s.endsWith(QLatin1String("px"), Qt::CaseInsensitive)) { + s.chop(2); + value.variant = s; + if (value.variant.convert(QVariant::Int)) { + font->setPixelSize(value.variant.toInt()); + valid = true; + } + } + return valid; +} + +static bool setFontStyleFromValue(const QCss::Value &value, QFont *font) +{ + if (value.type != Value::KnownIdentifier) + return false ; + switch (value.variant.toInt()) { + case Value_Normal: font->setStyle(QFont::StyleNormal); return true; + case Value_Italic: font->setStyle(QFont::StyleItalic); return true; + case Value_Oblique: font->setStyle(QFont::StyleOblique); return true; + default: break; + } + return false; +} + +static bool setFontWeightFromValue(const QCss::Value &value, QFont *font) +{ + if (value.type == Value::KnownIdentifier) { + switch (value.variant.toInt()) { + case Value_Normal: font->setWeight(QFont::Normal); return true; + case Value_Bold: font->setWeight(QFont::Bold); return true; + default: break; + } + return false; + } + if (value.type != Value::Number) + return false; + font->setWeight(qMin(value.variant.toInt() / 8, 99)); + return true; +} + +/** \internal + * parse the font family from the values (starting from index \a start) + * and set it the \a font + * \returns true if a family was extracted. + */ +static bool setFontFamilyFromValues(const QVector &values, QFont *font, int start = 0) +{ + QString family; + bool shouldAddSpace = false; + for (int i = start; i < values.count(); ++i) { + const QCss::Value &v = values.at(i); + if (v.type == Value::TermOperatorComma) { + family += QLatin1Char(','); + shouldAddSpace = false; + continue; + } + const QString str = v.variant.toString(); + if (str.isEmpty()) + break; + if (shouldAddSpace) + family += QLatin1Char(' '); + family += str; + shouldAddSpace = true; + } + if (family.isEmpty()) + return false; + font->setFamily(family); + return true; +} + +static void setTextDecorationFromValues(const QVector &values, QFont *font) +{ + for (int i = 0; i < values.count(); ++i) { + if (values.at(i).type != Value::KnownIdentifier) + continue; + switch (values.at(i).variant.toInt()) { + case Value_Underline: font->setUnderline(true); break; + case Value_Overline: font->setOverline(true); break; + case Value_LineThrough: font->setStrikeOut(true); break; + case Value_None: + font->setUnderline(false); + font->setOverline(false); + font->setStrikeOut(false); + break; + default: break; + } + } +} + +static void parseShorthandFontProperty(const QVector &values, QFont *font, int *fontSizeAdjustment) +{ + font->setStyle(QFont::StyleNormal); + font->setWeight(QFont::Normal); + *fontSizeAdjustment = -255; + + int i = 0; + while (i < values.count()) { + if (setFontStyleFromValue(values.at(i), font) + || setFontWeightFromValue(values.at(i), font)) + ++i; + else + break; + } + + if (i < values.count()) { + setFontSizeFromValue(values.at(i), font, fontSizeAdjustment); + ++i; + } + + if (i < values.count()) { + setFontFamilyFromValues(values, font, i); + } +} + +static void setFontVariantFromValue(const QCss::Value &value, QFont *font) +{ + if (value.type == Value::KnownIdentifier) { + switch (value.variant.toInt()) { + case Value_Normal: font->setCapitalization(QFont::MixedCase); break; + case Value_SmallCaps: font->setCapitalization(QFont::SmallCaps); break; + default: break; + } + } +} + +static void setTextTransformFromValue(const QCss::Value &value, QFont *font) +{ + if (value.type == Value::KnownIdentifier) { + switch (value.variant.toInt()) { + case Value_None: font->setCapitalization(QFont::MixedCase); break; + case Value_Uppercase: font->setCapitalization(QFont::AllUppercase); break; + case Value_Lowercase: font->setCapitalization(QFont::AllLowercase); break; + default: break; + } + } +} + +bool ValueExtractor::extractFont(QFont *font, int *fontSizeAdjustment) +{ + if (fontExtracted) { + *font = f; + *fontSizeAdjustment = adjustment; + return fontExtracted == 1; + } + + bool hit = false; + for (int i = 0; i < declarations.count(); ++i) { + const Declaration &decl = declarations.at(i); + if (decl.d->values.isEmpty()) + continue; + const QCss::Value &val = decl.d->values.at(0); + switch (decl.d->propertyId) { + case FontSize: setFontSizeFromValue(val, font, fontSizeAdjustment); break; + case FontStyle: setFontStyleFromValue(val, font); break; + case FontWeight: setFontWeightFromValue(val, font); break; + case FontFamily: setFontFamilyFromValues(decl.d->values, font); break; + case TextDecoration: setTextDecorationFromValues(decl.d->values, font); break; + case Font: parseShorthandFontProperty(decl.d->values, font, fontSizeAdjustment); break; + case FontVariant: setFontVariantFromValue(val, font); break; + case TextTransform: setTextTransformFromValue(val, font); break; + default: continue; + } + hit = true; + } + + f = *font; + adjustment = *fontSizeAdjustment; + fontExtracted = hit ? 1 : 2; + return hit; +} + +bool ValueExtractor::extractPalette(QBrush *fg, QBrush *sfg, QBrush *sbg, QBrush *abg) +{ + bool hit = false; + for (int i = 0; i < declarations.count(); ++i) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case Color: *fg = decl.brushValue(pal); break; + case QtSelectionForeground: *sfg = decl.brushValue(pal); break; + case QtSelectionBackground: *sbg = decl.brushValue(pal); break; + case QtAlternateBackground: *abg = decl.brushValue(pal); break; + default: continue; + } + hit = true; + } + return hit; +} + +void ValueExtractor::extractFont() +{ + if (fontExtracted) + return; + int dummy = -255; + extractFont(&f, &dummy); +} + +bool ValueExtractor::extractImage(QIcon *icon, Qt::Alignment *a, QSize *size) +{ + bool hit = false; + for (int i = 0; i < declarations.count(); ++i) { + const Declaration &decl = declarations.at(i); + switch (decl.d->propertyId) { + case QtImage: + *icon = decl.iconValue(); + if (decl.d->values.count() > 0 && decl.d->values.at(0).type == Value::Uri) { + // try to pull just the size from the image... + QImageReader imageReader(decl.d->values.at(0).variant.toString()); + if ((*size = imageReader.size()).isNull()) { + // but we'll have to load the whole image if the + // format doesn't support just reading the size + *size = imageReader.read().size(); + } + } + break; + case QtImageAlignment: *a = decl.alignmentValue(); break; + default: continue; + } + hit = true; + } + return hit; +} + +/////////////////////////////////////////////////////////////////////////////// +// Declaration +QColor Declaration::colorValue(const QPalette &pal) const +{ + if (d->values.count() != 1) + return QColor(); + + if (d->parsed.isValid()) { + if (d->parsed.type() == QVariant::Color) + return qvariant_cast(d->parsed); + if (d->parsed.type() == QVariant::Int) + return pal.color((QPalette::ColorRole)(d->parsed.toInt())); + } + + ColorData color = parseColorValue(d->values.at(0)); + if(color.type == ColorData::Role) { + d->parsed = QVariant::fromValue(color.role); + return pal.color((QPalette::ColorRole)(color.role)); + } else { + d->parsed = QVariant::fromValue(color.color); + return color.color; + } +} + +QBrush Declaration::brushValue(const QPalette &pal) const +{ + if (d->values.count() != 1) + return QBrush(); + + if (d->parsed.isValid()) { + if (d->parsed.type() == QVariant::Brush) + return qvariant_cast(d->parsed); + if (d->parsed.type() == QVariant::Int) + return pal.color((QPalette::ColorRole)(d->parsed.toInt())); + } + + BrushData data = parseBrushValue(d->values.at(0), pal); + + if(data.type == BrushData::Role) { + d->parsed = QVariant::fromValue(data.role); + return pal.color((QPalette::ColorRole)(data.role)); + } else { + if (data.type != BrushData::DependsOnThePalette) + d->parsed = QVariant::fromValue(data.brush); + return data.brush; + } +} + +void Declaration::brushValues(QBrush *c, const QPalette &pal) const +{ + int needParse = 0x1f; // bits 0..3 say if we should parse the corresponding value. + // the bit 4 say we need to update d->parsed + int i = 0; + if (d->parsed.isValid()) { + needParse = 0; + QList v = d->parsed.toList(); + for (i = 0; i < qMin(v.count(), 4); i++) { + if (v.at(i).type() == QVariant::Brush) { + c[i] = qvariant_cast(v.at(i)); + } else if (v.at(i).type() == QVariant::Int) { + c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt())); + } else { + needParse |= (1< v; + for (i = 0; i < qMin(d->values.count(), 4); i++) { + if (!(needParse & (1<values.at(i), pal); + if(data.type == BrushData::Role) { + v += QVariant::fromValue(data.role); + c[i] = pal.color((QPalette::ColorRole)(data.role)); + } else { + if (data.type != BrushData::DependsOnThePalette) { + v += QVariant::fromValue(data.brush); + } else { + v += QVariant(); + } + c[i] = data.brush; + } + } + if (needParse & 0x10) + d->parsed = v; + } + if (i == 0) c[0] = c[1] = c[2] = c[3] = QBrush(); + else if (i == 1) c[3] = c[2] = c[1] = c[0]; + else if (i == 2) c[2] = c[0], c[3] = c[1]; + else if (i == 3) c[3] = c[1]; +} + +bool Declaration::realValue(qreal *real, const char *unit) const +{ + if (d->values.count() != 1) + return false; + const Value &v = d->values.at(0); + if (unit && v.type != Value::Length) + return false; + QString s = v.variant.toString(); + if (unit) { + if (!s.endsWith(QLatin1String(unit), Qt::CaseInsensitive)) + return false; + s.chop(qstrlen(unit)); + } + bool ok = false; + qreal val = s.toDouble(&ok); + if (ok) + *real = val; + return ok; +} + +static bool intValueHelper(const QCss::Value &v, int *i, const char *unit) +{ + if (unit && v.type != Value::Length) + return false; + QString s = v.variant.toString(); + if (unit) { + if (!s.endsWith(QLatin1String(unit), Qt::CaseInsensitive)) + return false; + s.chop(qstrlen(unit)); + } + bool ok = false; + int val = s.toInt(&ok); + if (ok) + *i = val; + return ok; +} + +bool Declaration::intValue(int *i, const char *unit) const +{ + if (d->values.count() != 1) + return false; + return intValueHelper(d->values.at(0), i, unit); +} + +QSize Declaration::sizeValue() const +{ + if (d->parsed.isValid()) + return qvariant_cast(d->parsed); + + int x[2] = { 0, 0 }; + if (d->values.count() > 0) + intValueHelper(d->values.at(0), &x[0], "px"); + if (d->values.count() > 1) + intValueHelper(d->values.at(1), &x[1], "px"); + else + x[1] = x[0]; + QSize size(x[0], x[1]); + d->parsed = QVariant::fromValue(size); + return size; +} + +QRect Declaration::rectValue() const +{ + if (d->values.count() != 1) + return QRect(); + + if (d->parsed.isValid()) + return qvariant_cast(d->parsed); + + const QCss::Value &v = d->values.at(0); + if (v.type != Value::Function) + return QRect(); + QStringList func = v.variant.toStringList(); + if (func.count() != 2 || func.at(0).compare(QLatin1String("rect")) != 0) + return QRect(); + QStringList args = func[1].split(QLatin1Char(' '), QString::SkipEmptyParts); + if (args.count() != 4) + return QRect(); + QRect rect(args[0].toInt(), args[1].toInt(), args[2].toInt(), args[3].toInt()); + d->parsed = QVariant::fromValue(rect); + return rect; +} + +void Declaration::colorValues(QColor *c, const QPalette &pal) const +{ + int i; + if (d->parsed.isValid()) { + QList v = d->parsed.toList(); + for (i = 0; i < qMin(d->values.count(), 4); i++) { + if (v.at(i).type() == QVariant::Color) { + c[i] = qvariant_cast(v.at(i)); + } else { + c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt())); + } + } + } else { + QList v; + for (i = 0; i < qMin(d->values.count(), 4); i++) { + ColorData color = parseColorValue(d->values.at(i)); + if(color.type == ColorData::Role) { + v += QVariant::fromValue(color.role); + c[i] = pal.color((QPalette::ColorRole)(color.role)); + } else { + v += QVariant::fromValue(color.color); + c[i] = color.color; + } + } + d->parsed = v; + } + + if (i == 0) c[0] = c[1] = c[2] = c[3] = QColor(); + else if (i == 1) c[3] = c[2] = c[1] = c[0]; + else if (i == 2) c[2] = c[0], c[3] = c[1]; + else if (i == 3) c[3] = c[1]; +} + +BorderStyle Declaration::styleValue() const +{ + if (d->values.count() != 1) + return BorderStyle_None; + return parseStyleValue(d->values.at(0)); +} + +void Declaration::styleValues(BorderStyle *s) const +{ + int i; + for (i = 0; i < qMin(d->values.count(), 4); i++) + s[i] = parseStyleValue(d->values.at(i)); + if (i == 0) s[0] = s[1] = s[2] = s[3] = BorderStyle_None; + else if (i == 1) s[3] = s[2] = s[1] = s[0]; + else if (i == 2) s[2] = s[0], s[3] = s[1]; + else if (i == 3) s[3] = s[1]; +} + +Repeat Declaration::repeatValue() const +{ + if (d->parsed.isValid()) + return static_cast(d->parsed.toInt()); + if (d->values.count() != 1) + return Repeat_Unknown; + int v = findKnownValue(d->values.at(0).variant.toString(), + repeats, NumKnownRepeats); + d->parsed = v; + return static_cast(v); +} + +Origin Declaration::originValue() const +{ + if (d->parsed.isValid()) + return static_cast(d->parsed.toInt()); + if (d->values.count() != 1) + return Origin_Unknown; + int v = findKnownValue(d->values.at(0).variant.toString(), + origins, NumKnownOrigins); + d->parsed = v; + return static_cast(v); +} + +PositionMode Declaration::positionValue() const +{ + if (d->parsed.isValid()) + return static_cast(d->parsed.toInt()); + if (d->values.count() != 1) + return PositionMode_Unknown; + int v = findKnownValue(d->values.at(0).variant.toString(), + positions, NumKnownPositionModes); + d->parsed = v; + return static_cast(v); +} + +Attachment Declaration::attachmentValue() const +{ + if (d->parsed.isValid()) + return static_cast(d->parsed.toInt()); + if (d->values.count() != 1) + return Attachment_Unknown; + int v = findKnownValue(d->values.at(0).variant.toString(), + attachments, NumKnownAttachments); + d->parsed = v; + return static_cast(v); +} + +int Declaration::styleFeaturesValue() const +{ + Q_ASSERT(d->propertyId == QtStyleFeatures); + if (d->parsed.isValid()) + return d->parsed.toInt(); + int features = StyleFeature_None; + for (int i = 0; i < d->values.count(); i++) { + features |= static_cast(findKnownValue(d->values.value(i).variant.toString(), + styleFeatures, NumKnownStyleFeatures)); + } + d->parsed = features; + return features; +} + +QString Declaration::uriValue() const +{ + if (d->values.isEmpty() || d->values.at(0).type != Value::Uri) + return QString(); + return d->values.at(0).variant.toString(); +} + +Qt::Alignment Declaration::alignmentValue() const +{ + if (d->parsed.isValid()) + return Qt::Alignment(d->parsed.toInt()); + if (d->values.isEmpty() || d->values.count() > 2) + return Qt::AlignLeft | Qt::AlignTop; + + Qt::Alignment v = parseAlignment(d->values.constData(), d->values.count()); + d->parsed = int(v); + return v; +} + +void Declaration::borderImageValue(QString *image, int *cuts, + TileMode *h, TileMode *v) const +{ + *image = uriValue(); + for (int i = 0; i < 4; i++) + cuts[i] = -1; + *h = *v = TileMode_Stretch; + + if (d->values.count() < 2) + return; + + if (d->values.at(1).type == Value::Number) { // cuts! + int i; + for (i = 0; i < qMin(d->values.count()-1, 4); i++) { + const Value& v = d->values.at(i+1); + if (v.type != Value::Number) + break; + cuts[i] = v.variant.toString().toInt(); + } + if (i == 0) cuts[0] = cuts[1] = cuts[2] = cuts[3] = 0; + else if (i == 1) cuts[3] = cuts[2] = cuts[1] = cuts[0]; + else if (i == 2) cuts[2] = cuts[0], cuts[3] = cuts[1]; + else if (i == 3) cuts[3] = cuts[1]; + } + + if (d->values.last().type == Value::Identifier) { + *v = static_cast(findKnownValue(d->values.last().variant.toString(), + tileModes, NumKnownTileModes)); + } + if (d->values[d->values.count() - 2].type == Value::Identifier) { + *h = static_cast + (findKnownValue(d->values[d->values.count()-2].variant.toString(), + tileModes, NumKnownTileModes)); + } else + *h = *v; +} + +QIcon Declaration::iconValue() const +{ + if (d->parsed.isValid()) + return qvariant_cast(d->parsed); + + QIcon icon; + for (int i = 0; i < d->values.count();) { + const Value &value = d->values.at(i++); + if (value.type != Value::Uri) + break; + QString uri = value.variant.toString(); + QIcon::Mode mode = QIcon::Normal; + QIcon::State state = QIcon::Off; + for (int j = 0; j < 2; j++) { + if (i != d->values.count() && d->values.at(i).type == Value::KnownIdentifier) { + switch (d->values.at(i).variant.toInt()) { + case Value_Disabled: mode = QIcon::Disabled; break; + case Value_Active: mode = QIcon::Active; break; + case Value_Selected: mode = QIcon::Selected; break; + case Value_Normal: mode = QIcon::Normal; break; + case Value_On: state = QIcon::On; break; + case Value_Off: state = QIcon::Off; break; + default: break; + } + ++i; + } else { + break; + } + } + + // QIcon is soo broken + if (icon.isNull()) + icon = QIcon(uri); + else + icon.addPixmap(uri, mode, state); + + if (i == d->values.count()) + break; + + if (d->values.at(i).type == Value::TermOperatorComma) + i++; + } + + d->parsed = QVariant::fromValue(icon); + return icon; +} + +/////////////////////////////////////////////////////////////////////////////// +// Selector +int Selector::specificity() const +{ + int val = 0; + for (int i = 0; i < basicSelectors.count(); ++i) { + const BasicSelector &sel = basicSelectors.at(i); + if (!sel.elementName.isEmpty()) + val += 1; + + val += (sel.pseudos.count() + sel.attributeSelectors.count()) * 0x10; + val += sel.ids.count() * 0x100; + } + return val; +} + +QString Selector::pseudoElement() const +{ + const BasicSelector& bs = basicSelectors.last(); + if (!bs.pseudos.isEmpty() && bs.pseudos.at(0).type == PseudoClass_Unknown) + return bs.pseudos.at(0).name; + return QString(); +} + +quint64 Selector::pseudoClass(quint64 *negated) const +{ + const BasicSelector& bs = basicSelectors.last(); + if (bs.pseudos.isEmpty()) + return PseudoClass_Unspecified; + quint64 pc = PseudoClass_Unknown; + for (int i = !pseudoElement().isEmpty(); i < bs.pseudos.count(); i++) { + const Pseudo &pseudo = bs.pseudos.at(i); + if (pseudo.type == PseudoClass_Unknown) + return PseudoClass_Unknown; + if (!pseudo.negated) + pc |= pseudo.type; + else if (negated) + *negated |= pseudo.type; + } + return pc; +} + +/////////////////////////////////////////////////////////////////////////////// +// StyleSheet +void StyleSheet::buildIndexes(Qt::CaseSensitivity nameCaseSensitivity) +{ + QVector universals; + for (int i = 0; i < styleRules.count(); ++i) { + const StyleRule &rule = styleRules.at(i); + QVector universalsSelectors; + for (int j = 0; j < rule.selectors.count(); ++j) { + const Selector& selector = rule.selectors.at(j); + + if (selector.basicSelectors.isEmpty()) + continue; + + if (selector.basicSelectors.at(0).relationToNext == BasicSelector::NoRelation) { + if (selector.basicSelectors.count() != 1) + continue; + } else if (selector.basicSelectors.count() <= 1) { + continue; + } + + const BasicSelector &sel = selector.basicSelectors.at(selector.basicSelectors.count() - 1); + + if (!sel.ids.isEmpty()) { + StyleRule nr; + nr.selectors += selector; + nr.declarations = rule.declarations; + nr.order = i; + idIndex.insert(sel.ids.at(0), nr); + } else if (!sel.elementName.isEmpty()) { + StyleRule nr; + nr.selectors += selector; + nr.declarations = rule.declarations; + nr.order = i; + QString name = sel.elementName; + if (nameCaseSensitivity == Qt::CaseInsensitive) + name=name.toLower(); + nameIndex.insert(name, nr); + } else { + universalsSelectors += selector; + } + } + if (!universalsSelectors.isEmpty()) { + StyleRule nr; + nr.selectors = universalsSelectors; + nr.declarations = rule.declarations; + nr.order = i; + universals << nr; + } + } + styleRules = universals; +} + +/////////////////////////////////////////////////////////////////////////////// +// StyleSelector +StyleSelector::~StyleSelector() +{ +} + +bool StyleSelector::nodeNameEquals(NodePtr node, const QString& nodeName) const +{ + return nodeNames(node).contains(nodeName, nameCaseSensitivity); +} + +QStringList StyleSelector::nodeIds(NodePtr node) const +{ + return QStringList(attribute(node, QLatin1String("id"))); +} + +bool StyleSelector::selectorMatches(const Selector &selector, NodePtr node) +{ + if (selector.basicSelectors.isEmpty()) + return false; + + if (selector.basicSelectors.at(0).relationToNext == BasicSelector::NoRelation) { + if (selector.basicSelectors.count() != 1) + return false; + return basicSelectorMatches(selector.basicSelectors.at(0), node); + } + if (selector.basicSelectors.count() <= 1) + return false; + + int i = selector.basicSelectors.count() - 1; + node = duplicateNode(node); + bool match = true; + + BasicSelector sel = selector.basicSelectors.at(i); + do { + match = basicSelectorMatches(sel, node); + if (!match) { + if (sel.relationToNext == BasicSelector::MatchNextSelectorIfParent + || i == selector.basicSelectors.count() - 1) // first element must always match! + break; + } + + if (match || sel.relationToNext != BasicSelector::MatchNextSelectorIfAncestor) + --i; + + if (i < 0) + break; + + sel = selector.basicSelectors.at(i); + if (sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor + || sel.relationToNext == BasicSelector::MatchNextSelectorIfParent) { + + NodePtr nextParent = parentNode(node); + freeNode(node); + node = nextParent; + } else if (sel.relationToNext == BasicSelector::MatchNextSelectorIfPreceeds) { + NodePtr previousSibling = previousSiblingNode(node); + freeNode(node); + node = previousSibling; + } + if (isNullNode(node)) { + match = false; + break; + } + } while (i >= 0 && (match || sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor)); + + freeNode(node); + + return match; +} + +bool StyleSelector::basicSelectorMatches(const BasicSelector &sel, NodePtr node) +{ + if (!sel.attributeSelectors.isEmpty()) { + if (!hasAttributes(node)) + return false; + + for (int i = 0; i < sel.attributeSelectors.count(); ++i) { + const QCss::AttributeSelector &a = sel.attributeSelectors.at(i); + + const QString attrValue = attribute(node, a.name); + if (attrValue.isNull()) + return false; + + if (a.valueMatchCriterium == QCss::AttributeSelector::MatchContains) { + + QStringList lst = attrValue.split(QLatin1Char(' ')); + if (!lst.contains(a.value)) + return false; + } else if ( + (a.valueMatchCriterium == QCss::AttributeSelector::MatchEqual + && attrValue != a.value) + || + (a.valueMatchCriterium == QCss::AttributeSelector::MatchBeginsWith + && !attrValue.startsWith(a.value)) + ) + return false; + } + } + + if (!sel.elementName.isEmpty() + && !nodeNameEquals(node, sel.elementName)) + return false; + + if (!sel.ids.isEmpty() + && sel.ids != nodeIds(node)) + return false; + + return true; +} + +void StyleSelector::matchRule(NodePtr node, const StyleRule &rule, StyleSheetOrigin origin, + int depth, QMap *weightedRules) +{ + for (int j = 0; j < rule.selectors.count(); ++j) { + const Selector& selector = rule.selectors.at(j); + if (selectorMatches(selector, node)) { + uint weight = rule.order + + selector.specificity() *0x100 + + (uint(origin) + depth)*0x100000; + StyleRule newRule = rule; + if(rule.selectors.count() > 1) { + newRule.selectors.resize(1); + newRule.selectors[0] = selector; + } + //We might have rules with the same weight if they came from a rule with several selectors + weightedRules->insertMulti(weight, newRule); + } + } +} + +// Returns style rules that are in ascending order of specificity +// Each of the StyleRule returned will contain exactly one Selector +QVector StyleSelector::styleRulesForNode(NodePtr node) +{ + QVector rules; + if (styleSheets.isEmpty()) + return rules; + + QMap weightedRules; // (spec, rule) that will be sorted below + + //prune using indexed stylesheet + for (int sheetIdx = 0; sheetIdx < styleSheets.count(); ++sheetIdx) { + const StyleSheet &styleSheet = styleSheets.at(sheetIdx); + for (int i = 0; i < styleSheet.styleRules.count(); ++i) { + matchRule(node, styleSheet.styleRules.at(i), styleSheet.origin, styleSheet.depth, &weightedRules); + } + + if (!styleSheet.idIndex.isEmpty()) { + QStringList ids = nodeIds(node); + for (int i = 0; i < ids.count(); i++) { + const QString &key = ids.at(i); + QMultiHash::const_iterator it = styleSheet.idIndex.constFind(key); + while (it != styleSheet.idIndex.constEnd() && it.key() == key) { + matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules); + ++it; + } + } + } + if (!styleSheet.nameIndex.isEmpty()) { + QStringList names = nodeNames(node); + for (int i = 0; i < names.count(); i++) { + QString name = names.at(i); + if (nameCaseSensitivity == Qt::CaseInsensitive) + name = name.toLower(); + QMultiHash::const_iterator it = styleSheet.nameIndex.constFind(name); + while (it != styleSheet.nameIndex.constEnd() && it.key() == name) { + matchRule(node, it.value(), styleSheet.origin, styleSheet.depth, &weightedRules); + ++it; + } + } + } + if (!medium.isEmpty()) { + for (int i = 0; i < styleSheet.mediaRules.count(); ++i) { + if (styleSheet.mediaRules.at(i).media.contains(medium, Qt::CaseInsensitive)) { + for (int j = 0; j < styleSheet.mediaRules.at(i).styleRules.count(); ++j) { + matchRule(node, styleSheet.mediaRules.at(i).styleRules.at(j), styleSheet.origin, + styleSheet.depth, &weightedRules); + } + } + } + } + } + + rules.reserve(weightedRules.count()); + QMap::const_iterator it = weightedRules.constBegin(); + for ( ; it != weightedRules.constEnd() ; ++it) + rules += *it; + + return rules; +} + +// for qtexthtmlparser which requires just the declarations with Enabled state +// and without pseudo elements +QVector StyleSelector::declarationsForNode(NodePtr node, const char *extraPseudo) +{ + QVector decls; + QVector rules = styleRulesForNode(node); + for (int i = 0; i < rules.count(); i++) { + const Selector& selector = rules.at(i).selectors.at(0); + const QString pseudoElement = selector.pseudoElement(); + + if (extraPseudo && pseudoElement == QLatin1String(extraPseudo)) { + decls += rules.at(i).declarations; + continue; + } + + if (!pseudoElement.isEmpty()) // skip rules with pseudo elements + continue; + quint64 pseudoClass = selector.pseudoClass(); + if (pseudoClass == PseudoClass_Enabled || pseudoClass == PseudoClass_Unspecified) + decls += rules.at(i).declarations; + } + return decls; +} + +static inline bool isHexDigit(const char c) +{ + return (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F') + ; +} + +QString Scanner::preprocess(const QString &input, bool *hasEscapeSequences) +{ + QString output = input; + + if (hasEscapeSequences) + *hasEscapeSequences = false; + + int i = 0; + while (i < output.size()) { + if (output.at(i) == QLatin1Char('\\')) { + + ++i; + // test for unicode hex escape + int hexCount = 0; + const int hexStart = i; + while (i < output.size() + && isHexDigit(output.at(i).toLatin1()) + && hexCount < 7) { + ++hexCount; + ++i; + } + if (hexCount == 0) { + if (hasEscapeSequences) + *hasEscapeSequences = true; + continue; + } + + hexCount = qMin(hexCount, 6); + bool ok = false; + ushort code = output.mid(hexStart, hexCount).toUShort(&ok, 16); + if (ok) { + output.replace(hexStart - 1, hexCount + 1, QChar(code)); + i = hexStart; + } else { + i = hexStart; + } + } else { + ++i; + } + } + return output; +} + +int QCssScanner_Generated::handleCommentStart() +{ + while (pos < input.size() - 1) { + if (input.at(pos) == QLatin1Char('*') + && input.at(pos + 1) == QLatin1Char('/')) { + pos += 2; + break; + } + ++pos; + } + return S; +} + +void Scanner::scan(const QString &preprocessedInput, QVector *symbols) +{ + QCssScanner_Generated scanner(preprocessedInput); + Symbol sym; + int tok = scanner.lex(); + while (tok != -1) { + sym.token = static_cast(tok); + sym.text = scanner.input; + sym.start = scanner.lexemStart; + sym.len = scanner.lexemLength; + symbols->append(sym); + tok = scanner.lex(); + } +} + +QString Symbol::lexem() const +{ + QString result; + if (len > 0) + result.reserve(len); + for (int i = 0; i < len; ++i) { + if (text.at(start + i) == QLatin1Char('\\') && i < len - 1) + ++i; + result += text.at(start + i); + } + return result; +} + +Parser::Parser(const QString &css, bool isFile) +{ + init(css, isFile); +} + +Parser::Parser() +{ + index = 0; + errorIndex = -1; + hasEscapeSequences = false; +} + +void Parser::init(const QString &css, bool isFile) +{ + QString styleSheet = css; + if (isFile) { + QFile file(css); + if (file.open(QFile::ReadOnly)) { + sourcePath = QFileInfo(styleSheet).absolutePath() + QLatin1Char('/'); + QTextStream stream(&file); + styleSheet = stream.readAll(); + } else { + qWarning() << "QCss::Parser - Failed to load file " << css; + styleSheet.clear(); + } + } else { + sourcePath.clear(); + } + + hasEscapeSequences = false; + symbols.resize(0); + symbols.reserve(8); + Scanner::scan(Scanner::preprocess(styleSheet, &hasEscapeSequences), &symbols); + index = 0; + errorIndex = -1; +} + +bool Parser::parse(StyleSheet *styleSheet, Qt::CaseSensitivity nameCaseSensitivity) +{ + if (testTokenAndEndsWith(ATKEYWORD_SYM, QLatin1String("charset"))) { + if (!next(STRING)) return false; + if (!next(SEMICOLON)) return false; + } + + while (test(S) || test(CDO) || test(CDC)) {} + + while (testImport()) { + ImportRule rule; + if (!parseImport(&rule)) return false; + styleSheet->importRules.append(rule); + while (test(S) || test(CDO) || test(CDC)) {} + } + + do { + if (testMedia()) { + MediaRule rule; + if (!parseMedia(&rule)) return false; + styleSheet->mediaRules.append(rule); + } else if (testPage()) { + PageRule rule; + if (!parsePage(&rule)) return false; + styleSheet->pageRules.append(rule); + } else if (testRuleset()) { + StyleRule rule; + if (!parseRuleset(&rule)) return false; + styleSheet->styleRules.append(rule); + } else if (test(ATKEYWORD_SYM)) { + if (!until(RBRACE)) return false; + } else if (hasNext()) { + return false; + } + while (test(S) || test(CDO) || test(CDC)) {} + } while (hasNext()); + styleSheet->buildIndexes(nameCaseSensitivity); + return true; +} + +Symbol Parser::errorSymbol() +{ + if (errorIndex == -1) return Symbol(); + return symbols.at(errorIndex); +} + +static inline void removeOptionalQuotes(QString *str) +{ + if (!str->startsWith(QLatin1Char('\'')) + && !str->startsWith(QLatin1Char('\"'))) + return; + str->remove(0, 1); + str->chop(1); +} + +bool Parser::parseImport(ImportRule *importRule) +{ + skipSpace(); + + if (test(STRING)) { + importRule->href = lexem(); + } else { + if (!testAndParseUri(&importRule->href)) return false; + } + removeOptionalQuotes(&importRule->href); + + skipSpace(); + + if (testMedium()) { + if (!parseMedium(&importRule->media)) return false; + + while (test(COMMA)) { + skipSpace(); + if (!parseNextMedium(&importRule->media)) return false; + } + } + + if (!next(SEMICOLON)) return false; + + skipSpace(); + return true; +} + +bool Parser::parseMedia(MediaRule *mediaRule) +{ + do { + skipSpace(); + if (!parseNextMedium(&mediaRule->media)) return false; + } while (test(COMMA)); + + if (!next(LBRACE)) return false; + skipSpace(); + + while (testRuleset()) { + StyleRule rule; + if (!parseRuleset(&rule)) return false; + mediaRule->styleRules.append(rule); + } + + if (!next(RBRACE)) return false; + skipSpace(); + return true; +} + +bool Parser::parseMedium(QStringList *media) +{ + media->append(lexem()); + skipSpace(); + return true; +} + +bool Parser::parsePage(PageRule *pageRule) +{ + skipSpace(); + + if (testPseudoPage()) + if (!parsePseudoPage(&pageRule->selector)) return false; + + skipSpace(); + if (!next(LBRACE)) return false; + + do { + skipSpace(); + Declaration decl; + if (!parseNextDeclaration(&decl)) return false; + if (!decl.isEmpty()) + pageRule->declarations.append(decl); + } while (test(SEMICOLON)); + + if (!next(RBRACE)) return false; + skipSpace(); + return true; +} + +bool Parser::parsePseudoPage(QString *selector) +{ + if (!next(IDENT)) return false; + *selector = lexem(); + return true; +} + +bool Parser::parseNextOperator(Value *value) +{ + if (!hasNext()) return true; + switch (next()) { + case SLASH: value->type = Value::TermOperatorSlash; skipSpace(); break; + case COMMA: value->type = Value::TermOperatorComma; skipSpace(); break; + default: prev(); break; + } + return true; +} + +bool Parser::parseCombinator(BasicSelector::Relation *relation) +{ + *relation = BasicSelector::NoRelation; + if (lookup() == S) { + *relation = BasicSelector::MatchNextSelectorIfAncestor; + skipSpace(); + } else { + prev(); + } + if (test(PLUS)) { + *relation = BasicSelector::MatchNextSelectorIfPreceeds; + } else if (test(GREATER)) { + *relation = BasicSelector::MatchNextSelectorIfParent; + } + skipSpace(); + return true; +} + +bool Parser::parseProperty(Declaration *decl) +{ + decl->d->property = lexem(); + decl->d->propertyId = static_cast(findKnownValue(decl->d->property, properties, NumProperties)); + skipSpace(); + return true; +} + +bool Parser::parseRuleset(StyleRule *styleRule) +{ + Selector sel; + if (!parseSelector(&sel)) return false; + styleRule->selectors.append(sel); + + while (test(COMMA)) { + skipSpace(); + Selector sel; + if (!parseNextSelector(&sel)) return false; + styleRule->selectors.append(sel); + } + + skipSpace(); + if (!next(LBRACE)) return false; + const int declarationStart = index; + + do { + skipSpace(); + Declaration decl; + const int rewind = index; + if (!parseNextDeclaration(&decl)) { + index = rewind; + const bool foundSemicolon = until(SEMICOLON); + const int semicolonIndex = index; + + index = declarationStart; + const bool foundRBrace = until(RBRACE); + + if (foundSemicolon && semicolonIndex < index) { + decl = Declaration(); + index = semicolonIndex - 1; + } else { + skipSpace(); + return foundRBrace; + } + } + if (!decl.isEmpty()) + styleRule->declarations.append(decl); + } while (test(SEMICOLON)); + + if (!next(RBRACE)) return false; + skipSpace(); + return true; +} + +bool Parser::parseSelector(Selector *sel) +{ + BasicSelector basicSel; + if (!parseSimpleSelector(&basicSel)) return false; + while (testCombinator()) { + if (!parseCombinator(&basicSel.relationToNext)) return false; + + if (!testSimpleSelector()) break; + sel->basicSelectors.append(basicSel); + + basicSel = BasicSelector(); + if (!parseSimpleSelector(&basicSel)) return false; + } + sel->basicSelectors.append(basicSel); + return true; +} + +bool Parser::parseSimpleSelector(BasicSelector *basicSel) +{ + int minCount = 0; + if (lookupElementName()) { + if (!parseElementName(&basicSel->elementName)) return false; + } else { + prev(); + minCount = 1; + } + bool onceMore; + int count = 0; + do { + onceMore = false; + if (test(HASH)) { + QString theid = lexem(); + // chop off leading # + theid.remove(0, 1); + basicSel->ids.append(theid); + onceMore = true; + } else if (testClass()) { + onceMore = true; + AttributeSelector a; + a.name = QLatin1String("class"); + a.valueMatchCriterium = AttributeSelector::MatchContains; + if (!parseClass(&a.value)) return false; + basicSel->attributeSelectors.append(a); + } else if (testAttrib()) { + onceMore = true; + AttributeSelector a; + if (!parseAttrib(&a)) return false; + basicSel->attributeSelectors.append(a); + } else if (testPseudo()) { + onceMore = true; + Pseudo ps; + if (!parsePseudo(&ps)) return false; + basicSel->pseudos.append(ps); + } + if (onceMore) ++count; + } while (onceMore); + return count >= minCount; +} + +bool Parser::parseClass(QString *name) +{ + if (!next(IDENT)) return false; + *name = lexem(); + return true; +} + +bool Parser::parseElementName(QString *name) +{ + switch (lookup()) { + case STAR: name->clear(); break; + case IDENT: *name = lexem(); break; + default: return false; + } + return true; +} + +bool Parser::parseAttrib(AttributeSelector *attr) +{ + skipSpace(); + if (!next(IDENT)) return false; + attr->name = lexem(); + skipSpace(); + + if (test(EQUAL)) { + attr->valueMatchCriterium = AttributeSelector::MatchEqual; + } else if (test(INCLUDES)) { + attr->valueMatchCriterium = AttributeSelector::MatchContains; + } else if (test(DASHMATCH)) { + attr->valueMatchCriterium = AttributeSelector::MatchBeginsWith; + } else { + return next(RBRACKET); + } + + skipSpace(); + + if (!test(IDENT) && !test(STRING)) return false; + attr->value = unquotedLexem(); + + skipSpace(); + return next(RBRACKET); +} + +bool Parser::parsePseudo(Pseudo *pseudo) +{ + (void)test(COLON); + pseudo->negated = test(EXCLAMATION_SYM); + if (test(IDENT)) { + pseudo->name = lexem(); + pseudo->type = static_cast(findKnownValue(pseudo->name, pseudos, NumPseudos)); + return true; + } + if (!next(FUNCTION)) return false; + pseudo->function = lexem(); + // chop off trailing parenthesis + pseudo->function.chop(1); + skipSpace(); + if (!test(IDENT)) return false; + pseudo->name = lexem(); + skipSpace(); + return next(RPAREN); +} + +bool Parser::parseNextDeclaration(Declaration *decl) +{ + if (!testProperty()) + return true; // not an error! + if (!parseProperty(decl)) return false; + if (!next(COLON)) return false; + skipSpace(); + if (!parseNextExpr(&decl->d->values)) return false; + if (testPrio()) + if (!parsePrio(decl)) return false; + return true; +} + +bool Parser::testPrio() +{ + const int rewind = index; + if (!test(EXCLAMATION_SYM)) return false; + skipSpace(); + if (!test(IDENT)) { + index = rewind; + return false; + } + if (lexem().compare(QLatin1String("important"), Qt::CaseInsensitive) != 0) { + index = rewind; + return false; + } + return true; +} + +bool Parser::parsePrio(Declaration *declaration) +{ + declaration->d->important = true; + skipSpace(); + return true; +} + +bool Parser::parseExpr(QVector *values) +{ + Value val; + if (!parseTerm(&val)) return false; + values->append(val); + bool onceMore; + do { + onceMore = false; + val = Value(); + if (!parseNextOperator(&val)) return false; + if (val.type != QCss::Value::Unknown) + values->append(val); + if (testTerm()) { + onceMore = true; + val = Value(); + if (!parseTerm(&val)) return false; + values->append(val); + } + } while (onceMore); + return true; +} + +bool Parser::testTerm() +{ + return test(PLUS) || test(MINUS) + || test(NUMBER) + || test(PERCENTAGE) + || test(LENGTH) + || test(STRING) + || test(IDENT) + || testHexColor() + || testFunction(); +} + +bool Parser::parseTerm(Value *value) +{ + QString str = lexem(); + bool haveUnary = false; + if (lookup() == PLUS || lookup() == MINUS) { + haveUnary = true; + if (!hasNext()) return false; + next(); + str += lexem(); + } + + value->variant = str; + value->type = QCss::Value::String; + switch (lookup()) { + case NUMBER: + value->type = Value::Number; + value->variant.convert(QVariant::Double); + break; + case PERCENTAGE: + value->type = Value::Percentage; + str.chop(1); // strip off % + value->variant = str; + break; + case LENGTH: + value->type = Value::Length; + break; + + case STRING: + if (haveUnary) return false; + value->type = Value::String; + str.chop(1); + str.remove(0, 1); + value->variant = str; + break; + case IDENT: { + if (haveUnary) return false; + value->type = Value::Identifier; + const int theid = findKnownValue(str, values, NumKnownValues); + if (theid != 0) { + value->type = Value::KnownIdentifier; + value->variant = theid; + } + break; + } + default: { + if (haveUnary) return false; + prev(); + if (testHexColor()) { + QColor col; + if (!parseHexColor(&col)) return false; + value->type = Value::Color; + value->variant = col; + } else if (testFunction()) { + QString name, args; + if (!parseFunction(&name, &args)) return false; + if (name == QLatin1String("url")) { + value->type = Value::Uri; + removeOptionalQuotes(&args); + if (QFileInfo(args).isRelative() && !sourcePath.isEmpty()) { + args.prepend(sourcePath); + } + value->variant = args; + } else { + value->type = Value::Function; + value->variant = QStringList() << name << args; + } + } else { + return recordError(); + } + return true; + } + } + skipSpace(); + return true; +} + +bool Parser::parseFunction(QString *name, QString *args) +{ + *name = lexem(); + name->chop(1); + skipSpace(); + const int start = index; + if (!until(RPAREN)) return false; + for (int i = start; i < index - 1; ++i) + args->append(symbols.at(i).lexem()); + /* + if (!nextExpr(&arguments)) return false; + if (!next(RPAREN)) return false; + */ + skipSpace(); + return true; +} + +bool Parser::parseHexColor(QColor *col) +{ + col->setNamedColor(lexem()); + if (!col->isValid()) { + qWarning("QCssParser::parseHexColor: Unknown color name '%s'",lexem().toLatin1().constData()); + return false; + } + skipSpace(); + return true; +} + +bool Parser::testAndParseUri(QString *uri) +{ + const int rewind = index; + if (!testFunction()) return false; + + QString name, args; + if (!parseFunction(&name, &args)) { + index = rewind; + return false; + } + if (name.toLower() != QLatin1String("url")) { + index = rewind; + return false; + } + *uri = args; + removeOptionalQuotes(uri); + return true; +} + +bool Parser::testSimpleSelector() +{ + return testElementName() + || (test(HASH)) + || testClass() + || testAttrib() + || testPseudo(); +} + +bool Parser::next(QCss::TokenType t) +{ + if (hasNext() && next() == t) + return true; + return recordError(); +} + +bool Parser::test(QCss::TokenType t) +{ + if (index >= symbols.count()) + return false; + if (symbols.at(index).token == t) { + ++index; + return true; + } + return false; +} + +QString Parser::unquotedLexem() const +{ + QString s = lexem(); + if (lookup() == STRING) { + s.chop(1); + s.remove(0, 1); + } + return s; +} + +QString Parser::lexemUntil(QCss::TokenType t) +{ + QString lexem; + while (hasNext() && next() != t) + lexem += symbol().lexem(); + return lexem; +} + +bool Parser::until(QCss::TokenType target, QCss::TokenType target2) +{ + int braceCount = 0; + int brackCount = 0; + int parenCount = 0; + if (index) { + switch(symbols.at(index-1).token) { + case LBRACE: ++braceCount; break; + case LBRACKET: ++brackCount; break; + case FUNCTION: + case LPAREN: ++parenCount; break; + default: ; + } + } + while (index < symbols.size()) { + QCss::TokenType t = symbols.at(index++).token; + switch (t) { + case LBRACE: ++braceCount; break; + case RBRACE: --braceCount; break; + case LBRACKET: ++brackCount; break; + case RBRACKET: --brackCount; break; + case FUNCTION: + case LPAREN: ++parenCount; break; + case RPAREN: --parenCount; break; + default: break; + } + if ((t == target || (target2 != NONE && t == target2)) + && braceCount <= 0 + && brackCount <= 0 + && parenCount <= 0) + return true; + + if (braceCount < 0 || brackCount < 0 || parenCount < 0) { + --index; + break; + } + } + return false; +} + +bool Parser::testTokenAndEndsWith(QCss::TokenType t, const QLatin1String &str) +{ + if (!test(t)) return false; + if (!lexem().endsWith(str, Qt::CaseInsensitive)) { + prev(); + return false; + } + return true; +} + +QT_END_NAMESPACE +#endif // QT_NO_CSSPARSER diff --git a/src/gui/text/qcssparser_p.h b/src/gui/text/qcssparser_p.h new file mode 100644 index 0000000000..86bafc9215 --- /dev/null +++ b/src/gui/text/qcssparser_p.h @@ -0,0 +1,847 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCSSPARSER_P_H +#define QCSSPARSER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifndef QT_NO_CSSPARSER + +// VxWorks defines NONE as (-1) "for times when NULL won't do" +#if defined(Q_OS_VXWORKS) && defined(NONE) +# undef NONE +#endif +#if defined(Q_OS_INTEGRITY) +# undef Value +#endif + +QT_BEGIN_NAMESPACE + +namespace QCss +{ + +enum Property { + UnknownProperty, + BackgroundColor, + Color, + Float, + Font, + FontFamily, + FontSize, + FontStyle, + FontWeight, + Margin, + MarginBottom, + MarginLeft, + MarginRight, + MarginTop, + QtBlockIndent, + QtListIndent, + QtParagraphType, + QtTableType, + QtUserState, + TextDecoration, + TextIndent, + TextUnderlineStyle, + VerticalAlignment, + Whitespace, + QtSelectionForeground, + QtSelectionBackground, + Border, + BorderLeft, + BorderRight, + BorderTop, + BorderBottom, + Padding, + PaddingLeft, + PaddingRight, + PaddingTop, + PaddingBottom, + PageBreakBefore, + PageBreakAfter, + QtAlternateBackground, + BorderLeftStyle, + BorderRightStyle, + BorderTopStyle, + BorderBottomStyle, + BorderStyles, + BorderLeftColor, + BorderRightColor, + BorderTopColor, + BorderBottomColor, + BorderColor, + BorderLeftWidth, + BorderRightWidth, + BorderTopWidth, + BorderBottomWidth, + BorderWidth, + BorderTopLeftRadius, + BorderTopRightRadius, + BorderBottomLeftRadius, + BorderBottomRightRadius, + BorderRadius, + Background, + BackgroundOrigin, + BackgroundClip, + BackgroundRepeat, + BackgroundPosition, + BackgroundAttachment, + BackgroundImage, + BorderImage, + QtSpacing, + Width, + Height, + MinimumWidth, + MinimumHeight, + MaximumWidth, + MaximumHeight, + QtImage, + Left, + Right, + Top, + Bottom, + QtOrigin, + QtPosition, + Position, + QtStyleFeatures, + QtBackgroundRole, + ListStyleType, + ListStyle, + QtImageAlignment, + TextAlignment, + Outline, + OutlineOffset, + OutlineWidth, + OutlineColor, + OutlineStyle, + OutlineRadius, + OutlineTopLeftRadius, + OutlineTopRightRadius, + OutlineBottomLeftRadius, + OutlineBottomRightRadius, + FontVariant, + TextTransform, + QtListNumberPrefix, + QtListNumberSuffix, + LineHeight, + NumProperties +}; + +enum KnownValue { + UnknownValue, + Value_Normal, + Value_Pre, + Value_NoWrap, + Value_PreWrap, + Value_Small, + Value_Medium, + Value_Large, + Value_XLarge, + Value_XXLarge, + Value_Italic, + Value_Oblique, + Value_Bold, + Value_Underline, + Value_Overline, + Value_LineThrough, + Value_Sub, + Value_Super, + Value_Left, + Value_Right, + Value_Top, + Value_Bottom, + Value_Center, + Value_Native, + Value_Solid, + Value_Dotted, + Value_Dashed, + Value_DotDash, + Value_DotDotDash, + Value_Double, + Value_Groove, + Value_Ridge, + Value_Inset, + Value_Outset, + Value_Wave, + Value_Middle, + Value_Auto, + Value_Always, + Value_None, + Value_Transparent, + Value_Disc, + Value_Circle, + Value_Square, + Value_Decimal, + Value_LowerAlpha, + Value_UpperAlpha, + Value_LowerRoman, + Value_UpperRoman, + Value_SmallCaps, + Value_Uppercase, + Value_Lowercase, + + /* keep these in same order as QPalette::ColorRole */ + Value_FirstColorRole, + Value_WindowText = Value_FirstColorRole, + Value_Button, + Value_Light, + Value_Midlight, + Value_Dark, + Value_Mid, + Value_Text, + Value_BrightText, + Value_ButtonText, + Value_Base, + Value_Window, + Value_Shadow, + Value_Highlight, + Value_HighlightedText, + Value_Link, + Value_LinkVisited, + Value_AlternateBase, + Value_LastColorRole = Value_AlternateBase, + + Value_Disabled, + Value_Active, + Value_Selected, + Value_On, + Value_Off, + + NumKnownValues +}; + +enum BorderStyle { + BorderStyle_Unknown, + BorderStyle_None, + BorderStyle_Dotted, + BorderStyle_Dashed, + BorderStyle_Solid, + BorderStyle_Double, + BorderStyle_DotDash, + BorderStyle_DotDotDash, + BorderStyle_Groove, + BorderStyle_Ridge, + BorderStyle_Inset, + BorderStyle_Outset, + BorderStyle_Native, + NumKnownBorderStyles +}; + +enum Edge { + TopEdge, + RightEdge, + BottomEdge, + LeftEdge, + NumEdges +}; + +enum Corner { + TopLeftCorner, + TopRightCorner, + BottomLeftCorner, + BottomRightCorner +}; + +enum TileMode { + TileMode_Unknown, + TileMode_Round, + TileMode_Stretch, + TileMode_Repeat, + NumKnownTileModes +}; + +enum Repeat { + Repeat_Unknown, + Repeat_None, + Repeat_X, + Repeat_Y, + Repeat_XY, + NumKnownRepeats +}; + +enum Origin { + Origin_Unknown, + Origin_Padding, + Origin_Border, + Origin_Content, + Origin_Margin, + NumKnownOrigins +}; + +enum PositionMode { + PositionMode_Unknown, + PositionMode_Static, + PositionMode_Relative, + PositionMode_Absolute, + PositionMode_Fixed, + NumKnownPositionModes +}; + +enum Attachment { + Attachment_Unknown, + Attachment_Fixed, + Attachment_Scroll, + NumKnownAttachments +}; + +enum StyleFeature { + StyleFeature_None = 0, + StyleFeature_BackgroundColor = 1, + StyleFeature_BackgroundGradient = 2, + NumKnownStyleFeatures = 4 +}; + +struct Q_GUI_EXPORT Value +{ + enum Type { + Unknown, + Number, + Percentage, + Length, + String, + Identifier, + KnownIdentifier, + Uri, + Color, + Function, + TermOperatorSlash, + TermOperatorComma + }; + inline Value() : type(Unknown) { } + Type type; + QVariant variant; + QString toString() const; +}; + +struct ColorData { + ColorData() : role(QPalette::NoRole), type(Invalid) {} + ColorData(const QColor &col) : color(col), role(QPalette::NoRole), type(Color) {} + ColorData(QPalette::ColorRole r) : role(r), type(Role) {} + QColor color; + QPalette::ColorRole role; + enum { Invalid, Color, Role} type; +}; + +struct BrushData { + BrushData() : role(QPalette::NoRole), type(Invalid) {} + BrushData(const QBrush &br) : brush(br), role(QPalette::NoRole), type(Brush) {} + BrushData(QPalette::ColorRole r) : role(r), type(Role) {} + QBrush brush; + QPalette::ColorRole role; + enum { Invalid, Brush, Role, DependsOnThePalette } type; +}; + +struct BackgroundData { + BrushData brush; + QString image; + Repeat repeat; + Qt::Alignment alignment; +}; + +struct LengthData { + qreal number; + enum { None, Px, Ex, Em } unit; +}; + +struct BorderData { + LengthData width; + BorderStyle style; + BrushData color; +}; + + +// 1. StyleRule - x:hover, y:clicked > z:checked { prop1: value1; prop2: value2; } +// 2. QVector - x:hover, y:clicked z:checked +// 3. QVector - y:clicked z:checked +// 4. QVector - { prop1: value1; prop2: value2; } +// 5. Declaration - prop1: value1; + +struct Q_AUTOTEST_EXPORT Declaration +{ + struct DeclarationData : public QSharedData + { + inline DeclarationData() : propertyId(UnknownProperty), important(false) {} + QString property; + Property propertyId; + QVector values; + QVariant parsed; + bool important; + }; + QExplicitlySharedDataPointer d; + inline Declaration() : d(new DeclarationData()) {} + inline bool isEmpty() const { return d->property.isEmpty() && d->propertyId == UnknownProperty; } + + // helper functions + QColor colorValue(const QPalette & = QPalette()) const; + void colorValues(QColor *c, const QPalette & = QPalette()) const; + QBrush brushValue(const QPalette & = QPalette()) const; + void brushValues(QBrush *c, const QPalette & = QPalette()) const; + + BorderStyle styleValue() const; + void styleValues(BorderStyle *s) const; + + Origin originValue() const; + Repeat repeatValue() const; + Qt::Alignment alignmentValue() const; + PositionMode positionValue() const; + Attachment attachmentValue() const; + int styleFeaturesValue() const; + + bool intValue(int *i, const char *unit = 0) const; + bool realValue(qreal *r, const char *unit = 0) const; + + QSize sizeValue() const; + QRect rectValue() const; + QString uriValue() const; + QIcon iconValue() const; + + void borderImageValue(QString *image, int *cuts, TileMode *h, TileMode *v) const; +}; + +const quint64 PseudoClass_Unknown = Q_UINT64_C(0x0000000000000000); +const quint64 PseudoClass_Enabled = Q_UINT64_C(0x0000000000000001); +const quint64 PseudoClass_Disabled = Q_UINT64_C(0x0000000000000002); +const quint64 PseudoClass_Pressed = Q_UINT64_C(0x0000000000000004); +const quint64 PseudoClass_Focus = Q_UINT64_C(0x0000000000000008); +const quint64 PseudoClass_Hover = Q_UINT64_C(0x0000000000000010); +const quint64 PseudoClass_Checked = Q_UINT64_C(0x0000000000000020); +const quint64 PseudoClass_Unchecked = Q_UINT64_C(0x0000000000000040); +const quint64 PseudoClass_Indeterminate = Q_UINT64_C(0x0000000000000080); +const quint64 PseudoClass_Unspecified = Q_UINT64_C(0x0000000000000100); +const quint64 PseudoClass_Selected = Q_UINT64_C(0x0000000000000200); +const quint64 PseudoClass_Horizontal = Q_UINT64_C(0x0000000000000400); +const quint64 PseudoClass_Vertical = Q_UINT64_C(0x0000000000000800); +const quint64 PseudoClass_Window = Q_UINT64_C(0x0000000000001000); +const quint64 PseudoClass_Children = Q_UINT64_C(0x0000000000002000); +const quint64 PseudoClass_Sibling = Q_UINT64_C(0x0000000000004000); +const quint64 PseudoClass_Default = Q_UINT64_C(0x0000000000008000); +const quint64 PseudoClass_First = Q_UINT64_C(0x0000000000010000); +const quint64 PseudoClass_Last = Q_UINT64_C(0x0000000000020000); +const quint64 PseudoClass_Middle = Q_UINT64_C(0x0000000000040000); +const quint64 PseudoClass_OnlyOne = Q_UINT64_C(0x0000000000080000); +const quint64 PseudoClass_PreviousSelected = Q_UINT64_C(0x0000000000100000); +const quint64 PseudoClass_NextSelected = Q_UINT64_C(0x0000000000200000); +const quint64 PseudoClass_Flat = Q_UINT64_C(0x0000000000400000); +const quint64 PseudoClass_Left = Q_UINT64_C(0x0000000000800000); +const quint64 PseudoClass_Right = Q_UINT64_C(0x0000000001000000); +const quint64 PseudoClass_Top = Q_UINT64_C(0x0000000002000000); +const quint64 PseudoClass_Bottom = Q_UINT64_C(0x0000000004000000); +const quint64 PseudoClass_Exclusive = Q_UINT64_C(0x0000000008000000); +const quint64 PseudoClass_NonExclusive = Q_UINT64_C(0x0000000010000000); +const quint64 PseudoClass_Frameless = Q_UINT64_C(0x0000000020000000); +const quint64 PseudoClass_ReadOnly = Q_UINT64_C(0x0000000040000000); +const quint64 PseudoClass_Active = Q_UINT64_C(0x0000000080000000); +const quint64 PseudoClass_Closable = Q_UINT64_C(0x0000000100000000); +const quint64 PseudoClass_Movable = Q_UINT64_C(0x0000000200000000); +const quint64 PseudoClass_Floatable = Q_UINT64_C(0x0000000400000000); +const quint64 PseudoClass_Minimized = Q_UINT64_C(0x0000000800000000); +const quint64 PseudoClass_Maximized = Q_UINT64_C(0x0000001000000000); +const quint64 PseudoClass_On = Q_UINT64_C(0x0000002000000000); +const quint64 PseudoClass_Off = Q_UINT64_C(0x0000004000000000); +const quint64 PseudoClass_Editable = Q_UINT64_C(0x0000008000000000); +const quint64 PseudoClass_Item = Q_UINT64_C(0x0000010000000000); +const quint64 PseudoClass_Closed = Q_UINT64_C(0x0000020000000000); +const quint64 PseudoClass_Open = Q_UINT64_C(0x0000040000000000); +const quint64 PseudoClass_EditFocus = Q_UINT64_C(0x0000080000000000); +const quint64 PseudoClass_Alternate = Q_UINT64_C(0x0000100000000000); +// The Any specifier is never generated, but can be used as a wildcard in searches. +const quint64 PseudoClass_Any = Q_UINT64_C(0x0000ffffffffffff); +const int NumPseudos = 46; + +struct Pseudo +{ + Pseudo() : type(0), negated(false) { } + quint64 type; + QString name; + QString function; + bool negated; +}; + +struct AttributeSelector +{ + enum ValueMatchType { + NoMatch, + MatchEqual, + MatchContains, + MatchBeginsWith + }; + inline AttributeSelector() : valueMatchCriterium(NoMatch) {} + + QString name; + QString value; + ValueMatchType valueMatchCriterium; +}; + +struct BasicSelector +{ + inline BasicSelector() : relationToNext(NoRelation) {} + + enum Relation { + NoRelation, + MatchNextSelectorIfAncestor, + MatchNextSelectorIfParent, + MatchNextSelectorIfPreceeds + }; + + QString elementName; + + QStringList ids; + QVector pseudos; + QVector attributeSelectors; + + Relation relationToNext; +}; + +struct Q_AUTOTEST_EXPORT Selector +{ + QVector basicSelectors; + int specificity() const; + quint64 pseudoClass(quint64 *negated = 0) const; + QString pseudoElement() const; +}; + +struct StyleRule; +struct MediaRule; +struct PageRule; +struct ImportRule; + +struct Q_AUTOTEST_EXPORT ValueExtractor +{ + ValueExtractor(const QVector &declarations, const QPalette & = QPalette()); + + bool extractFont(QFont *font, int *fontSizeAdjustment); + bool extractBackground(QBrush *, QString *, Repeat *, Qt::Alignment *, QCss::Origin *, QCss::Attachment *, + QCss::Origin *); + bool extractGeometry(int *w, int *h, int *minw, int *minh, int *maxw, int *maxh); + bool extractPosition(int *l, int *t, int *r, int *b, QCss::Origin *, Qt::Alignment *, + QCss::PositionMode *, Qt::Alignment *); + bool extractBox(int *margins, int *paddings, int *spacing = 0); + bool extractBorder(int *borders, QBrush *colors, BorderStyle *Styles, QSize *radii); + bool extractOutline(int *borders, QBrush *colors, BorderStyle *Styles, QSize *radii, int *offsets); + bool extractPalette(QBrush *fg, QBrush *sfg, QBrush *sbg, QBrush *abg); + int extractStyleFeatures(); + bool extractImage(QIcon *icon, Qt::Alignment *a, QSize *size); + + int lengthValue(const Declaration &decl); + +private: + void extractFont(); + void borderValue(const Declaration &decl, int *width, QCss::BorderStyle *style, QBrush *color); + LengthData lengthValue(const Value& v); + void lengthValues(const Declaration &decl, int *m); + QSize sizeValue(const Declaration &decl); + void sizeValues(const Declaration &decl, QSize *radii); + + QVector declarations; + QFont f; + int adjustment; + int fontExtracted; + QPalette pal; +}; + +struct StyleRule +{ + StyleRule() : order(0) { } + QVector selectors; + QVector declarations; + int order; +}; + +struct MediaRule +{ + QStringList media; + QVector styleRules; +}; + +struct PageRule +{ + QString selector; + QVector declarations; +}; + +struct ImportRule +{ + QString href; + QStringList media; +}; + +enum StyleSheetOrigin { + StyleSheetOrigin_Unspecified, + StyleSheetOrigin_UserAgent, + StyleSheetOrigin_User, + StyleSheetOrigin_Author, + StyleSheetOrigin_Inline +}; + +struct StyleSheet +{ + StyleSheet() : origin(StyleSheetOrigin_Unspecified), depth(0) { } + QVector styleRules; //only contains rules that are not indexed + QVector mediaRules; + QVector pageRules; + QVector importRules; + StyleSheetOrigin origin; + int depth; // applicable only for inline style sheets + QMultiHash nameIndex; + QMultiHash idIndex; + void buildIndexes(Qt::CaseSensitivity nameCaseSensitivity = Qt::CaseSensitive); +}; + +class Q_GUI_EXPORT StyleSelector +{ +public: + StyleSelector() : nameCaseSensitivity(Qt::CaseSensitive) {} + virtual ~StyleSelector(); + + union NodePtr { + void *ptr; + int id; + }; + + QVector styleRulesForNode(NodePtr node); + QVector declarationsForNode(NodePtr node, const char *extraPseudo = 0); + + virtual bool nodeNameEquals(NodePtr node, const QString& nodeName) const; + virtual QString attribute(NodePtr node, const QString &name) const = 0; + virtual bool hasAttributes(NodePtr node) const = 0; + virtual QStringList nodeIds(NodePtr node) const; + virtual QStringList nodeNames(NodePtr node) const = 0; + virtual bool isNullNode(NodePtr node) const = 0; + virtual NodePtr parentNode(NodePtr node) const = 0; + virtual NodePtr previousSiblingNode(NodePtr node) const = 0; + virtual NodePtr duplicateNode(NodePtr node) const = 0; + virtual void freeNode(NodePtr node) const = 0; + + QVector styleSheets; + QString medium; + Qt::CaseSensitivity nameCaseSensitivity; +private: + void matchRule(NodePtr node, const StyleRule &rules, StyleSheetOrigin origin, + int depth, QMap *weightedRules); + bool selectorMatches(const Selector &rule, NodePtr node); + bool basicSelectorMatches(const BasicSelector &rule, NodePtr node); +}; + +enum TokenType { + NONE, + + S, + + CDO, + CDC, + INCLUDES, + DASHMATCH, + + LBRACE, + PLUS, + GREATER, + COMMA, + + STRING, + INVALID, + + IDENT, + + HASH, + + ATKEYWORD_SYM, + + EXCLAMATION_SYM, + + LENGTH, + + PERCENTAGE, + NUMBER, + + FUNCTION, + + COLON, + SEMICOLON, + RBRACE, + SLASH, + MINUS, + DOT, + STAR, + LBRACKET, + RBRACKET, + EQUAL, + LPAREN, + RPAREN, + OR +}; + +struct Q_GUI_EXPORT Symbol +{ + inline Symbol() : token(NONE), start(0), len(-1) {} + TokenType token; + QString text; + int start, len; + QString lexem() const; +}; + +class Q_AUTOTEST_EXPORT Scanner +{ +public: + static QString preprocess(const QString &input, bool *hasEscapeSequences = 0); + static void scan(const QString &preprocessedInput, QVector *symbols); +}; + +class Q_GUI_EXPORT Parser +{ +public: + Parser(); + Parser(const QString &css, bool file = false); + + void init(const QString &css, bool file = false); + bool parse(StyleSheet *styleSheet, Qt::CaseSensitivity nameCaseSensitivity = Qt::CaseSensitive); + Symbol errorSymbol(); + + bool parseImport(ImportRule *importRule); + bool parseMedia(MediaRule *mediaRule); + bool parseMedium(QStringList *media); + bool parsePage(PageRule *pageRule); + bool parsePseudoPage(QString *selector); + bool parseNextOperator(Value *value); + bool parseCombinator(BasicSelector::Relation *relation); + bool parseProperty(Declaration *decl); + bool parseRuleset(StyleRule *styleRule); + bool parseSelector(Selector *sel); + bool parseSimpleSelector(BasicSelector *basicSel); + bool parseClass(QString *name); + bool parseElementName(QString *name); + bool parseAttrib(AttributeSelector *attr); + bool parsePseudo(Pseudo *pseudo); + bool parseNextDeclaration(Declaration *declaration); + bool parsePrio(Declaration *declaration); + bool parseExpr(QVector *values); + bool parseTerm(Value *value); + bool parseFunction(QString *name, QString *args); + bool parseHexColor(QColor *col); + bool testAndParseUri(QString *uri); + + inline bool testRuleset() { return testSelector(); } + inline bool testSelector() { return testSimpleSelector(); } + inline bool parseNextSelector(Selector *sel) { if (!testSelector()) return recordError(); return parseSelector(sel); } + bool testSimpleSelector(); + inline bool parseNextSimpleSelector(BasicSelector *basicSel) { if (!testSimpleSelector()) return recordError(); return parseSimpleSelector(basicSel); } + inline bool testElementName() { return test(IDENT) || test(STAR); } + inline bool testClass() { return test(DOT); } + inline bool testAttrib() { return test(LBRACKET); } + inline bool testPseudo() { return test(COLON); } + inline bool testMedium() { return test(IDENT); } + inline bool parseNextMedium(QStringList *media) { if (!testMedium()) return recordError(); return parseMedium(media); } + inline bool testPseudoPage() { return test(COLON); } + inline bool testImport() { return testTokenAndEndsWith(ATKEYWORD_SYM, QLatin1String("import")); } + inline bool testMedia() { return testTokenAndEndsWith(ATKEYWORD_SYM, QLatin1String("media")); } + inline bool testPage() { return testTokenAndEndsWith(ATKEYWORD_SYM, QLatin1String("page")); } + inline bool testCombinator() { return test(PLUS) || test(GREATER) || test(S); } + inline bool testProperty() { return test(IDENT); } + bool testTerm(); + inline bool testExpr() { return testTerm(); } + inline bool parseNextExpr(QVector *values) { if (!testExpr()) return recordError(); return parseExpr(values); } + bool testPrio(); + inline bool testHexColor() { return test(HASH); } + inline bool testFunction() { return test(FUNCTION); } + inline bool parseNextFunction(QString *name, QString *args) { if (!testFunction()) return recordError(); return parseFunction(name, args); } + + inline bool lookupElementName() const { return lookup() == IDENT || lookup() == STAR; } + + inline void skipSpace() { while (test(S)) {}; } + + inline bool hasNext() const { return index < symbols.count(); } + inline TokenType next() { return symbols.at(index++).token; } + bool next(TokenType t); + bool test(TokenType t); + inline void prev() { index--; } + inline const Symbol &symbol() const { return symbols.at(index - 1); } + inline QString lexem() const { return symbol().lexem(); } + QString unquotedLexem() const; + QString lexemUntil(TokenType t); + bool until(TokenType target, TokenType target2 = NONE); + inline TokenType lookup() const { + return (index - 1) < symbols.count() ? symbols.at(index - 1).token : NONE; + } + + bool testTokenAndEndsWith(TokenType t, const QLatin1String &str); + + inline bool recordError() { errorIndex = index; return false; } + + QVector symbols; + int index; + int errorIndex; + bool hasEscapeSequences; + QString sourcePath; +}; + +} // namespace QCss + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE( QCss::BackgroundData ) +Q_DECLARE_METATYPE( QCss::LengthData ) +Q_DECLARE_METATYPE( QCss::BorderData ) + + +#endif // QT_NO_CSSPARSER + +#endif diff --git a/src/gui/text/qcssscanner.cpp b/src/gui/text/qcssscanner.cpp new file mode 100644 index 0000000000..409b86969c --- /dev/null +++ b/src/gui/text/qcssscanner.cpp @@ -0,0 +1,1146 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// auto generated. DO NOT EDIT. +class QCssScanner_Generated +{ +public: + QCssScanner_Generated(const QString &inp); + + inline QChar next() { + return (pos < input.length()) ? input.at(pos++) : QChar(); + } + int handleCommentStart(); + int lex(); + + QString input; + int pos; + int lexemStart; + int lexemLength; +}; + +QCssScanner_Generated::QCssScanner_Generated(const QString &inp) +{ + input = inp; + pos = 0; + lexemStart = 0; + lexemLength = 0; +} + + +int QCssScanner_Generated::lex() +{ + lexemStart = pos; + lexemLength = 0; + int lastAcceptingPos = -1; + int token = -1; + QChar ch; + + // initial state + ch = next(); + if (ch.unicode() >= 9 && ch.unicode() <= 10) + goto state_1; + if (ch.unicode() >= 12 && ch.unicode() <= 13) + goto state_1; + if (ch.unicode() == 32) + goto state_1; + if (ch.unicode() == 33) { + token = QCss::EXCLAMATION_SYM; + goto found; + } + if (ch.unicode() == 34) + goto state_3; + if (ch.unicode() == 35) + goto state_4; + if (ch.unicode() == 39) + goto state_5; + if (ch.unicode() == 40) { + token = QCss::LPAREN; + goto found; + } + if (ch.unicode() == 41) { + token = QCss::RPAREN; + goto found; + } + if (ch.unicode() == 42) { + token = QCss::STAR; + goto found; + } + if (ch.unicode() == 43) + goto state_9; + if (ch.unicode() == 44) + goto state_10; + if (ch.unicode() == 45) + goto state_11; + if (ch.unicode() == 46) + goto state_12; + if (ch.unicode() == 47) + goto state_13; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_14; + if (ch.unicode() == 58) { + token = QCss::COLON; + goto found; + } + if (ch.unicode() == 59) { + token = QCss::SEMICOLON; + goto found; + } + if (ch.unicode() == 60) + goto state_17; + if (ch.unicode() == 61) { + token = QCss::EQUAL; + goto found; + } + if (ch.unicode() == 62) + goto state_19; + if (ch.unicode() == 64) + goto state_20; + if (ch.unicode() == 91) { + token = QCss::LBRACKET; + goto found; + } + if (ch.unicode() == 92) + goto state_22; + if (ch.unicode() == 93) { + token = QCss::RBRACKET; + goto found; + } + if (ch.unicode() == 95) + goto state_24; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_24; + if (ch.unicode() == 123) + goto state_25; + if (ch.unicode() == 124) + goto state_26; + if (ch.unicode() == 125) { + token = QCss::RBRACE; + goto found; + } + if (ch.unicode() == 126) + goto state_28; + goto out; + state_1: + lastAcceptingPos = pos; + token = QCss::S; + ch = next(); + if (ch.unicode() >= 9 && ch.unicode() <= 10) + goto state_29; + if (ch.unicode() >= 12 && ch.unicode() <= 13) + goto state_29; + if (ch.unicode() == 32) + goto state_29; + if (ch.unicode() == 43) + goto state_9; + if (ch.unicode() == 44) + goto state_10; + if (ch.unicode() == 62) + goto state_19; + if (ch.unicode() == 123) + goto state_25; + goto out; + state_3: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_4: + ch = next(); + if (ch.unicode() == 45) + goto state_33; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_33; + if (ch.unicode() == 92) + goto state_34; + if (ch.unicode() == 95) + goto state_33; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_33; + goto out; + state_5: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_9: + lastAcceptingPos = pos; + token = QCss::PLUS; + goto out; + state_10: + lastAcceptingPos = pos; + token = QCss::COMMA; + goto out; + state_11: + lastAcceptingPos = pos; + token = QCss::MINUS; + ch = next(); + if (ch.unicode() == 45) + goto state_38; + if (ch.unicode() == 92) + goto state_22; + if (ch.unicode() == 95) + goto state_24; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_24; + goto out; + state_12: + lastAcceptingPos = pos; + token = QCss::DOT; + ch = next(); + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_39; + goto out; + state_13: + lastAcceptingPos = pos; + token = QCss::SLASH; + ch = next(); + if (ch.unicode() == 42) { + token = handleCommentStart(); + goto found; + } + goto out; + state_14: + lastAcceptingPos = pos; + token = QCss::NUMBER; + ch = next(); + if (ch.unicode() == 37) + goto state_41; + if (ch.unicode() == 45) + goto state_42; + if (ch.unicode() == 46) + goto state_43; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_44; + if (ch.unicode() == 92) + goto state_45; + if (ch.unicode() == 95) + goto state_46; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_46; + goto out; + state_17: + ch = next(); + if (ch.unicode() == 33) + goto state_47; + goto out; + state_19: + lastAcceptingPos = pos; + token = QCss::GREATER; + goto out; + state_20: + ch = next(); + if (ch.unicode() == 45) + goto state_48; + if (ch.unicode() == 92) + goto state_49; + if (ch.unicode() == 95) + goto state_50; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_50; + goto out; + state_22: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_51; + if (ch.unicode() == 11) + goto state_51; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_51; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_51; + if (ch.unicode() >= 103) + goto state_51; + goto out; + state_24: + lastAcceptingPos = pos; + token = QCss::IDENT; + ch = next(); + if (ch.unicode() == 40) + goto state_52; + if (ch.unicode() == 45) + goto state_53; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_53; + if (ch.unicode() == 92) + goto state_54; + if (ch.unicode() == 95) + goto state_53; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_53; + goto out; + state_25: + lastAcceptingPos = pos; + token = QCss::LBRACE; + goto out; + state_26: + lastAcceptingPos = pos; + token = QCss::OR; + ch = next(); + if (ch.unicode() == 61) { + token = QCss::DASHMATCH; + goto found; + } + goto out; + state_28: + ch = next(); + if (ch.unicode() == 61) { + token = QCss::INCLUDES; + goto found; + } + goto out; + state_29: + lastAcceptingPos = pos; + token = QCss::S; + ch = next(); + if (ch.unicode() >= 9 && ch.unicode() <= 10) + goto state_29; + if (ch.unicode() >= 12 && ch.unicode() <= 13) + goto state_29; + if (ch.unicode() == 32) + goto state_29; + if (ch.unicode() == 43) + goto state_9; + if (ch.unicode() == 44) + goto state_10; + if (ch.unicode() == 62) + goto state_19; + if (ch.unicode() == 123) + goto state_25; + goto out; + state_30: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_31: + lastAcceptingPos = pos; + token = QCss::STRING; + goto out; + state_32: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_57; + if (ch.unicode() == 10) + goto state_58; + if (ch.unicode() == 11) + goto state_57; + if (ch.unicode() == 12) + goto state_59; + if (ch.unicode() == 13) + goto state_60; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_57; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_57; + if (ch.unicode() >= 103) + goto state_57; + goto out; + state_33: + lastAcceptingPos = pos; + token = QCss::HASH; + ch = next(); + if (ch.unicode() == 45) + goto state_61; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_61; + if (ch.unicode() == 92) + goto state_62; + if (ch.unicode() == 95) + goto state_61; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_61; + goto out; + state_34: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_63; + if (ch.unicode() == 11) + goto state_63; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_63; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_63; + if (ch.unicode() >= 103) + goto state_63; + goto out; + state_35: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_36: + lastAcceptingPos = pos; + token = QCss::STRING; + goto out; + state_37: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_64; + if (ch.unicode() == 10) + goto state_65; + if (ch.unicode() == 11) + goto state_64; + if (ch.unicode() == 12) + goto state_66; + if (ch.unicode() == 13) + goto state_67; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_64; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_64; + if (ch.unicode() >= 103) + goto state_64; + goto out; + state_38: + ch = next(); + if (ch.unicode() == 62) { + token = QCss::CDC; + goto found; + } + goto out; + state_39: + lastAcceptingPos = pos; + token = QCss::NUMBER; + ch = next(); + if (ch.unicode() == 37) + goto state_41; + if (ch.unicode() == 45) + goto state_42; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_69; + if (ch.unicode() == 92) + goto state_45; + if (ch.unicode() == 95) + goto state_46; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_46; + goto out; + state_41: + lastAcceptingPos = pos; + token = QCss::PERCENTAGE; + goto out; + state_42: + ch = next(); + if (ch.unicode() == 92) + goto state_45; + if (ch.unicode() == 95) + goto state_46; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_46; + goto out; + state_43: + ch = next(); + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_39; + goto out; + state_44: + lastAcceptingPos = pos; + token = QCss::NUMBER; + ch = next(); + if (ch.unicode() == 37) + goto state_41; + if (ch.unicode() == 45) + goto state_42; + if (ch.unicode() == 46) + goto state_43; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_44; + if (ch.unicode() == 92) + goto state_45; + if (ch.unicode() == 95) + goto state_46; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_46; + goto out; + state_45: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_70; + if (ch.unicode() == 11) + goto state_70; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_70; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_70; + if (ch.unicode() >= 103) + goto state_70; + goto out; + state_46: + lastAcceptingPos = pos; + token = QCss::LENGTH; + ch = next(); + if (ch.unicode() == 45) + goto state_71; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_71; + if (ch.unicode() == 92) + goto state_72; + if (ch.unicode() == 95) + goto state_71; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_71; + goto out; + state_47: + ch = next(); + if (ch.unicode() == 45) + goto state_73; + goto out; + state_48: + ch = next(); + if (ch.unicode() == 92) + goto state_49; + if (ch.unicode() == 95) + goto state_50; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_50; + goto out; + state_49: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_74; + if (ch.unicode() == 11) + goto state_74; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_74; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_74; + if (ch.unicode() >= 103) + goto state_74; + goto out; + state_50: + lastAcceptingPos = pos; + token = QCss::ATKEYWORD_SYM; + ch = next(); + if (ch.unicode() == 45) + goto state_75; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_75; + if (ch.unicode() == 92) + goto state_76; + if (ch.unicode() == 95) + goto state_75; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_75; + goto out; + state_51: + lastAcceptingPos = pos; + token = QCss::IDENT; + ch = next(); + if (ch.unicode() == 40) + goto state_52; + if (ch.unicode() == 45) + goto state_53; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_53; + if (ch.unicode() == 92) + goto state_54; + if (ch.unicode() == 95) + goto state_53; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_53; + goto out; + state_52: + lastAcceptingPos = pos; + token = QCss::FUNCTION; + goto out; + state_53: + lastAcceptingPos = pos; + token = QCss::IDENT; + ch = next(); + if (ch.unicode() == 40) + goto state_52; + if (ch.unicode() == 45) + goto state_53; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_53; + if (ch.unicode() == 92) + goto state_54; + if (ch.unicode() == 95) + goto state_53; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_53; + goto out; + state_54: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_77; + if (ch.unicode() == 11) + goto state_77; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_77; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_77; + if (ch.unicode() >= 103) + goto state_77; + goto out; + state_57: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_58: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_59: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_60: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 10) + goto state_78; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_61: + lastAcceptingPos = pos; + token = QCss::HASH; + ch = next(); + if (ch.unicode() == 45) + goto state_61; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_61; + if (ch.unicode() == 92) + goto state_62; + if (ch.unicode() == 95) + goto state_61; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_61; + goto out; + state_62: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_79; + if (ch.unicode() == 11) + goto state_79; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_79; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_79; + if (ch.unicode() >= 103) + goto state_79; + goto out; + state_63: + lastAcceptingPos = pos; + token = QCss::HASH; + ch = next(); + if (ch.unicode() == 45) + goto state_61; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_61; + if (ch.unicode() == 92) + goto state_62; + if (ch.unicode() == 95) + goto state_61; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_61; + goto out; + state_64: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_65: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_66: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_67: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 10) + goto state_80; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_69: + lastAcceptingPos = pos; + token = QCss::NUMBER; + ch = next(); + if (ch.unicode() == 37) + goto state_41; + if (ch.unicode() == 45) + goto state_42; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_69; + if (ch.unicode() == 92) + goto state_45; + if (ch.unicode() == 95) + goto state_46; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_46; + goto out; + state_70: + lastAcceptingPos = pos; + token = QCss::LENGTH; + ch = next(); + if (ch.unicode() == 45) + goto state_71; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_71; + if (ch.unicode() == 92) + goto state_72; + if (ch.unicode() == 95) + goto state_71; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_71; + goto out; + state_71: + lastAcceptingPos = pos; + token = QCss::LENGTH; + ch = next(); + if (ch.unicode() == 45) + goto state_71; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_71; + if (ch.unicode() == 92) + goto state_72; + if (ch.unicode() == 95) + goto state_71; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_71; + goto out; + state_72: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_81; + if (ch.unicode() == 11) + goto state_81; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_81; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_81; + if (ch.unicode() >= 103) + goto state_81; + goto out; + state_73: + ch = next(); + if (ch.unicode() == 45) { + token = QCss::CDO; + goto found; + } + goto out; + state_74: + lastAcceptingPos = pos; + token = QCss::ATKEYWORD_SYM; + ch = next(); + if (ch.unicode() == 45) + goto state_75; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_75; + if (ch.unicode() == 92) + goto state_76; + if (ch.unicode() == 95) + goto state_75; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_75; + goto out; + state_75: + lastAcceptingPos = pos; + token = QCss::ATKEYWORD_SYM; + ch = next(); + if (ch.unicode() == 45) + goto state_75; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_75; + if (ch.unicode() == 92) + goto state_76; + if (ch.unicode() == 95) + goto state_75; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_75; + goto out; + state_76: + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_83; + if (ch.unicode() == 11) + goto state_83; + if (ch.unicode() >= 14 && ch.unicode() <= 47) + goto state_83; + if (ch.unicode() >= 58 && ch.unicode() <= 96) + goto state_83; + if (ch.unicode() >= 103) + goto state_83; + goto out; + state_77: + lastAcceptingPos = pos; + token = QCss::IDENT; + ch = next(); + if (ch.unicode() == 40) + goto state_52; + if (ch.unicode() == 45) + goto state_53; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_53; + if (ch.unicode() == 92) + goto state_54; + if (ch.unicode() == 95) + goto state_53; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_53; + goto out; + state_78: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_30; + if (ch.unicode() == 11) + goto state_30; + if (ch.unicode() >= 14 && ch.unicode() <= 33) + goto state_30; + if (ch.unicode() == 34) + goto state_31; + if (ch.unicode() >= 35 && ch.unicode() <= 91) + goto state_30; + if (ch.unicode() == 92) + goto state_32; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_30; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_30; + if (ch.unicode() >= 123) + goto state_30; + goto out; + state_79: + lastAcceptingPos = pos; + token = QCss::HASH; + ch = next(); + if (ch.unicode() == 45) + goto state_61; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_61; + if (ch.unicode() == 92) + goto state_62; + if (ch.unicode() == 95) + goto state_61; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_61; + goto out; + state_80: + lastAcceptingPos = pos; + token = QCss::INVALID; + ch = next(); + if (ch.unicode() >= 1 && ch.unicode() <= 9) + goto state_35; + if (ch.unicode() == 11) + goto state_35; + if (ch.unicode() >= 14 && ch.unicode() <= 38) + goto state_35; + if (ch.unicode() == 39) + goto state_36; + if (ch.unicode() >= 40 && ch.unicode() <= 91) + goto state_35; + if (ch.unicode() == 92) + goto state_37; + if (ch.unicode() >= 93 && ch.unicode() <= 96) + goto state_35; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_35; + if (ch.unicode() >= 123) + goto state_35; + goto out; + state_81: + lastAcceptingPos = pos; + token = QCss::LENGTH; + ch = next(); + if (ch.unicode() == 45) + goto state_71; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_71; + if (ch.unicode() == 92) + goto state_72; + if (ch.unicode() == 95) + goto state_71; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_71; + goto out; + state_83: + lastAcceptingPos = pos; + token = QCss::ATKEYWORD_SYM; + ch = next(); + if (ch.unicode() == 45) + goto state_75; + if (ch.unicode() >= 48 && ch.unicode() <= 57) + goto state_75; + if (ch.unicode() == 92) + goto state_76; + if (ch.unicode() == 95) + goto state_75; + if ((ch.unicode() >= 'a' && ch.unicode() <= 'z') || (ch.unicode() >= 'A' && ch.unicode() <= 'Z') || ch.unicode() >= 256) + goto state_75; + goto out; + found: + lastAcceptingPos = pos; + + out: + if (lastAcceptingPos != -1) { + lexemLength = lastAcceptingPos - lexemStart; + pos = lastAcceptingPos; + } + return token; +} + diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp new file mode 100644 index 0000000000..b8cfb1f902 --- /dev/null +++ b/src/gui/text/qfont.cpp @@ -0,0 +1,3188 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfont.h" +#include "qdebug.h" +#include "qpaintdevice.h" +#include "qfontdatabase.h" +#include "qfontmetrics.h" +#include "qfontinfo.h" +#include "qpainter.h" +#include "qhash.h" +#include "qdatastream.h" +#include "qapplication.h" +#include "qstringlist.h" + +#include "qthread.h" +#include "qthreadstorage.h" + +#include +#include "qfont_p.h" +#include +#include +#include +#include + +#ifdef Q_WS_X11 +#include "qx11info_x11.h" +#include +#endif +#ifdef Q_WS_QWS +#include "qscreen_qws.h" +#if !defined(QT_NO_QWS_QPF2) +#include +#include "qfontengine_qpf_p.h" +#endif +#endif +#ifdef Q_OS_SYMBIAN +#include +#endif +#ifdef Q_WS_QPA +#include +#include +#endif + +#include + +// #define QFONTCACHE_DEBUG +#ifdef QFONTCACHE_DEBUG +# define FC_DEBUG qDebug +#else +# define FC_DEBUG if (false) qDebug +#endif + +QT_BEGIN_NAMESPACE + +#ifdef Q_WS_WIN +extern HDC shared_dc(); +#endif + +#ifdef Q_WS_X11 +extern const QX11Info *qt_x11Info(const QPaintDevice *pd); +#endif + +bool QFontDef::exactMatch(const QFontDef &other) const +{ + /* + QFontDef comparison is more complicated than just simple + per-member comparisons. + + When comparing point/pixel sizes, either point or pixelsize + could be -1. in This case we have to compare the non negative + size value. + + This test will fail if the point-sizes differ by 1/2 point or + more or they do not round to the same value. We have to do this + since our API still uses 'int' point-sizes in the API, but store + deci-point-sizes internally. + + To compare the family members, we need to parse the font names + and compare the family/foundry strings separately. This allows + us to compare e.g. "Helvetica" and "Helvetica [Adobe]" with + positive results. + */ + if (pixelSize != -1 && other.pixelSize != -1) { + if (pixelSize != other.pixelSize) + return false; + } else if (pointSize != -1 && other.pointSize != -1) { + if (pointSize != other.pointSize) + return false; + } else { + return false; + } + + if (!ignorePitch && !other.ignorePitch && fixedPitch != other.fixedPitch) + return false; + + if (stretch != 0 && other.stretch != 0 && stretch != other.stretch) + return false; + + QString this_family, this_foundry, other_family, other_foundry; + QFontDatabase::parseFontName(family, this_foundry, this_family); + QFontDatabase::parseFontName(other.family, other_foundry, other_family); + + this_family = QFontDatabase::resolveFontFamilyAlias(this_family); + other_family = QFontDatabase::resolveFontFamilyAlias(other_family); + + return (styleHint == other.styleHint + && styleStrategy == other.styleStrategy + && weight == other.weight + && style == other.style + && this_family == other_family + && (this_foundry.isEmpty() + || other_foundry.isEmpty() + || this_foundry == other_foundry) +#ifdef Q_WS_X11 + && addStyle == other.addStyle +#endif // Q_WS_X11 + ); +} + +extern bool qt_is_gui_used; + +Q_GUI_EXPORT int qt_defaultDpiX() +{ + if (!qt_is_gui_used) + return 75; + + int dpi; +#ifdef Q_WS_X11 + dpi = QX11Info::appDpiX(); +#elif defined(Q_WS_WIN) + dpi = GetDeviceCaps(shared_dc(),LOGPIXELSX); +#elif defined(Q_WS_MAC) + extern float qt_mac_defaultDpi_x(); //qpaintdevice_mac.cpp + dpi = qt_mac_defaultDpi_x(); +#elif defined(Q_WS_QWS) + if (!qt_screen) + return 72; + QScreen *screen = qt_screen; + const QList subScreens = qt_screen->subScreens(); + if (!subScreens.isEmpty()) + screen = subScreens.at(0); + dpi = qRound(screen->width() / (screen->physicalWidth() / qreal(25.4))); +#elif defined(Q_WS_QPA) + QPlatformIntegration *pi = QApplicationPrivate::platformIntegration(); + if (pi) { + QPlatformScreen *screen = QApplicationPrivate::platformIntegration()->screens().at(0); + const QSize screenSize = screen->geometry().size(); + const QSize physicalSize = screen->physicalSize(); + dpi = qRound(screenSize.width() / (physicalSize.width() / qreal(25.4))); + } else { + //PI has not been initialised, or it is being initialised. Give a default dpi + dpi = 100; + } +#elif defined(Q_OS_SYMBIAN) + dpi = S60->defaultDpiX; +#endif // Q_WS_X11 + + return dpi; +} + +Q_GUI_EXPORT int qt_defaultDpiY() +{ + if (!qt_is_gui_used) + return 75; + + int dpi; +#ifdef Q_WS_X11 + dpi = QX11Info::appDpiY(); +#elif defined(Q_WS_WIN) + dpi = GetDeviceCaps(shared_dc(),LOGPIXELSY); +#elif defined(Q_WS_MAC) + extern float qt_mac_defaultDpi_y(); //qpaintdevice_mac.cpp + dpi = qt_mac_defaultDpi_y(); +#elif defined(Q_WS_QWS) + if (!qt_screen) + return 72; + QScreen *screen = qt_screen; + const QList subScreens = qt_screen->subScreens(); + if (!subScreens.isEmpty()) + screen = subScreens.at(0); + dpi = qRound(screen->height() / (screen->physicalHeight() / qreal(25.4))); +#elif defined(Q_WS_QPA) + QPlatformIntegration *pi = QApplicationPrivate::platformIntegration(); + if (pi) { + QPlatformScreen *screen = QApplicationPrivate::platformIntegration()->screens().at(0); + const QSize screenSize = screen->geometry().size(); + const QSize physicalSize = screen->physicalSize(); + dpi = qRound(screenSize.height() / (physicalSize.height() / qreal(25.4))); + } else { + //PI has not been initialised, or it is being initialised. Give a default dpi + dpi = 100; + } +#elif defined(Q_OS_SYMBIAN) + dpi = S60->defaultDpiY; +#endif // Q_WS_X11 + + return dpi; +} + +Q_GUI_EXPORT int qt_defaultDpi() +{ + return qt_defaultDpiY(); +} + +QFontPrivate::QFontPrivate() + : engineData(0), dpi(qt_defaultDpi()), screen(0), + rawMode(false), underline(false), overline(false), strikeOut(false), kerning(true), + capital(0), letterSpacingIsAbsolute(false), scFont(0) +{ +#ifdef Q_WS_X11 + if (QX11Info::display()) + screen = QX11Info::appScreen(); + else + screen = 0; +#endif +#ifdef Q_WS_WIN + hdc = 0; +#endif +} + +QFontPrivate::QFontPrivate(const QFontPrivate &other) + : request(other.request), engineData(0), dpi(other.dpi), screen(other.screen), + rawMode(other.rawMode), underline(other.underline), overline(other.overline), + strikeOut(other.strikeOut), kerning(other.kerning), + capital(other.capital), letterSpacingIsAbsolute(other.letterSpacingIsAbsolute), + letterSpacing(other.letterSpacing), wordSpacing(other.wordSpacing), + scFont(other.scFont) +{ +#ifdef Q_WS_WIN + hdc = other.hdc; +#endif + if (scFont && scFont != this) + scFont->ref.ref(); +} + +QFontPrivate::~QFontPrivate() +{ + if (engineData) + engineData->ref.deref(); + engineData = 0; + if (scFont && scFont != this) + scFont->ref.deref(); + scFont = 0; +} + +extern QMutex *qt_fontdatabase_mutex(); + +#if !defined(Q_WS_MAC) +#define QT_FONT_ENGINE_FROM_DATA(data, script) data->engines[script] +#else +#define QT_FONT_ENGINE_FROM_DATA(data, script) data->engine +#endif + +QFontEngine *QFontPrivate::engineForScript(int script) const +{ + QMutexLocker locker(qt_fontdatabase_mutex()); + if (script >= QUnicodeTables::Inherited) + script = QUnicodeTables::Common; + if (engineData && engineData->fontCache != QFontCache::instance()) { + // throw out engineData that came from a different thread + engineData->ref.deref(); + engineData = 0; + } + if (!engineData || !QT_FONT_ENGINE_FROM_DATA(engineData, script)) + QFontDatabase::load(this, script); + return QT_FONT_ENGINE_FROM_DATA(engineData, script); +} + +void QFontPrivate::alterCharForCapitalization(QChar &c) const { + switch (capital) { + case QFont::AllUppercase: + case QFont::SmallCaps: + c = c.toUpper(); + break; + case QFont::AllLowercase: + c = c.toLower(); + break; + case QFont::MixedCase: + break; + } +} + +QFontPrivate *QFontPrivate::smallCapsFontPrivate() const +{ + if (scFont) + return scFont; + QFont font(const_cast(this)); + qreal pointSize = font.pointSizeF(); + if (pointSize > 0) + font.setPointSizeF(pointSize * .7); + else + font.setPixelSize((font.pixelSize() * 7 + 5) / 10); + scFont = font.d.data(); + if (scFont != this) + scFont->ref.ref(); + return scFont; +} + + +void QFontPrivate::resolve(uint mask, const QFontPrivate *other) +{ + Q_ASSERT(other != 0); + + dpi = other->dpi; + + if ((mask & QFont::AllPropertiesResolved) == QFont::AllPropertiesResolved) return; + + // assign the unset-bits with the set-bits of the other font def + if (! (mask & QFont::FamilyResolved)) + request.family = other->request.family; + + if (! (mask & QFont::SizeResolved)) { + request.pointSize = other->request.pointSize; + request.pixelSize = other->request.pixelSize; + } + + if (! (mask & QFont::StyleHintResolved)) + request.styleHint = other->request.styleHint; + + if (! (mask & QFont::StyleStrategyResolved)) + request.styleStrategy = other->request.styleStrategy; + + if (! (mask & QFont::WeightResolved)) + request.weight = other->request.weight; + + if (! (mask & QFont::StyleResolved)) + request.style = other->request.style; + + if (! (mask & QFont::FixedPitchResolved)) + request.fixedPitch = other->request.fixedPitch; + + if (! (mask & QFont::StretchResolved)) + request.stretch = other->request.stretch; + + if (! (mask & QFont::HintingPreferenceResolved)) + request.hintingPreference = other->request.hintingPreference; + + if (! (mask & QFont::UnderlineResolved)) + underline = other->underline; + + if (! (mask & QFont::OverlineResolved)) + overline = other->overline; + + if (! (mask & QFont::StrikeOutResolved)) + strikeOut = other->strikeOut; + + if (! (mask & QFont::KerningResolved)) + kerning = other->kerning; + + if (! (mask & QFont::LetterSpacingResolved)) { + letterSpacing = other->letterSpacing; + letterSpacingIsAbsolute = other->letterSpacingIsAbsolute; + } + if (! (mask & QFont::WordSpacingResolved)) + wordSpacing = other->wordSpacing; + if (! (mask & QFont::CapitalizationResolved)) + capital = other->capital; +} + + + + +QFontEngineData::QFontEngineData() + : ref(1), fontCache(QFontCache::instance()) +{ +#if !defined(Q_WS_MAC) + memset(engines, 0, QUnicodeTables::ScriptCount * sizeof(QFontEngine *)); +#else + engine = 0; +#endif +} + +QFontEngineData::~QFontEngineData() +{ +#if !defined(Q_WS_MAC) + for (int i = 0; i < QUnicodeTables::ScriptCount; ++i) { + if (engines[i]) + engines[i]->ref.deref(); + engines[i] = 0; + } +#else + if (engine) + engine->ref.deref(); + engine = 0; +#endif // Q_WS_X11 || Q_WS_WIN || Q_WS_MAC +} + + + + +/*! + \class QFont + \reentrant + + \brief The QFont class specifies a font used for drawing text. + + \ingroup painting + \ingroup appearance + \ingroup shared + \ingroup richtext-processing + + + When you create a QFont object you specify various attributes that + you want the font to have. Qt will use the font with the specified + attributes, or if no matching font exists, Qt will use the closest + matching installed font. The attributes of the font that is + actually used are retrievable from a QFontInfo object. If the + window system provides an exact match exactMatch() returns true. + Use QFontMetrics to get measurements, e.g. the pixel length of a + string using QFontMetrics::width(). + + Note that a QApplication instance must exist before a QFont can be + used. You can set the application's default font with + QApplication::setFont(). + + If a chosen font does not include all the characters that + need to be displayed, QFont will try to find the characters in the + nearest equivalent fonts. When a QPainter draws a character from a + font the QFont will report whether or not it has the character; if + it does not, QPainter will draw an unfilled square. + + Create QFonts like this: + + \snippet doc/src/snippets/code/src_gui_text_qfont.cpp 0 + + The attributes set in the constructor can also be set later, e.g. + setFamily(), setPointSize(), setPointSizeFloat(), setWeight() and + setItalic(). The remaining attributes must be set after + contstruction, e.g. setBold(), setUnderline(), setOverline(), + setStrikeOut() and setFixedPitch(). QFontInfo objects should be + created \e after the font's attributes have been set. A QFontInfo + object will not change, even if you change the font's + attributes. The corresponding "get" functions, e.g. family(), + pointSize(), etc., return the values that were set, even though + the values used may differ. The actual values are available from a + QFontInfo object. + + If the requested font family is unavailable you can influence the + \link #fontmatching font matching algorithm\endlink by choosing a + particular \l{QFont::StyleHint} and \l{QFont::StyleStrategy} with + setStyleHint(). The default family (corresponding to the current + style hint) is returned by defaultFamily(). + + The font-matching algorithm has a lastResortFamily() and + lastResortFont() in cases where a suitable match cannot be found. + You can provide substitutions for font family names using + insertSubstitution() and insertSubstitutions(). Substitutions can + be removed with removeSubstitution(). Use substitute() to retrieve + a family's first substitute, or the family name itself if it has + no substitutes. Use substitutes() to retrieve a list of a family's + substitutes (which may be empty). + + Every QFont has a key() which you can use, for example, as the key + in a cache or dictionary. If you want to store a user's font + preferences you could use QSettings, writing the font information + with toString() and reading it back with fromString(). The + operator<<() and operator>>() functions are also available, but + they work on a data stream. + + It is possible to set the height of characters shown on the screen + to a specified number of pixels with setPixelSize(); however using + setPointSize() has a similar effect and provides device + independence. + + In X11 you can set a font using its system + specific name with setRawName(). + + Loading fonts can be expensive, especially on X11. QFont contains + extensive optimizations to make the copying of QFont objects fast, + and to cache the results of the slow window system functions it + depends upon. + + \target fontmatching + The font matching algorithm works as follows: + \list 1 + \o The specified font family is searched for. + \o If not found, the styleHint() is used to select a replacement + family. + \o Each replacement font family is searched for. + \o If none of these are found or there was no styleHint(), "helvetica" + will be searched for. + \o If "helvetica" isn't found Qt will try the lastResortFamily(). + \o If the lastResortFamily() isn't found Qt will try the + lastResortFont() which will always return a name of some kind. + \endlist + + Note that the actual font matching algorithm varies from platform to platform. + + In Windows a request for the "Courier" font is automatically changed to + "Courier New", an improved version of Courier that allows for smooth scaling. + The older "Courier" bitmap font can be selected by setting the PreferBitmap + style strategy (see setStyleStrategy()). + + Once a font is found, the remaining attributes are matched in order of + priority: + \list 1 + \o fixedPitch() + \o pointSize() (see below) + \o weight() + \o style() + \endlist + + If you have a font which matches on family, even if none of the + other attributes match, this font will be chosen in preference to + a font which doesn't match on family but which does match on the + other attributes. This is because font family is the dominant + search criteria. + + The point size is defined to match if it is within 20% of the + requested point size. When several fonts match and are only + distinguished by point size, the font with the closest point size + to the one requested will be chosen. + + The actual family, font size, weight and other font attributes + used for drawing text will depend on what's available for the + chosen family under the window system. A QFontInfo object can be + used to determine the actual values used for drawing the text. + + Examples: + + \snippet doc/src/snippets/code/src_gui_text_qfont.cpp 1 + If you had both an Adobe and a Cronyx Helvetica, you might get + either. + + \snippet doc/src/snippets/code/src_gui_text_qfont.cpp 2 + + You can specify the foundry you want in the family name. The font f + in the above example will be set to "Helvetica + [Cronyx]". + + To determine the attributes of the font actually used in the window + system, use a QFontInfo object, e.g. + + \snippet doc/src/snippets/code/src_gui_text_qfont.cpp 3 + + To find out font metrics use a QFontMetrics object, e.g. + + \snippet doc/src/snippets/code/src_gui_text_qfont.cpp 4 + + For more general information on fonts, see the + \link http://nwalsh.com/comp.fonts/FAQ/ comp.fonts FAQ.\endlink + Information on encodings can be found from + \link http://czyborra.com/ Roman Czyborra's\endlink page. + + \sa QFontComboBox, QFontMetrics, QFontInfo, QFontDatabase, {Character Map Example} +*/ + +/*! + \internal + \enum QFont::ResolveProperties + + This enum describes the properties of a QFont that can be set on a font + individually and then considered resolved. + + \value FamilyResolved + \value SizeResolved + \value StyleHintResolved + \value StyleStrategyResolved + \value WeightResolved + \value StyleResolved + \value UnderlineResolved + \value OverlineResolved + \value StrikeOutResolved + \value FixedPitchResolved + \value StretchResolved + \value KerningResolved + \value CapitalizationResolved + \value LetterSpacingResolved + \value WordSpacingResolved + \value CompletelyResolved +*/ + +/*! + \enum QFont::Style + + This enum describes the different styles of glyphs that are used to + display text. + + \value StyleNormal Normal glyphs used in unstyled text. + \value StyleItalic Italic glyphs that are specifically designed for + the purpose of representing italicized text. + \value StyleOblique Glyphs with an italic appearance that are typically + based on the unstyled glyphs, but are not fine-tuned + for the purpose of representing italicized text. + + \sa Weight +*/ + +/*! + \fn Qt::HANDLE QFont::handle() const + + Returns the window system handle to the font, for low-level + access. Using this function is \e not portable. +*/ + +/*! + \fn FT_Face QFont::freetypeFace() const + + Returns the handle to the primary FreeType face of the font. If font merging is not disabled a + QFont can contain several physical fonts. + + Returns 0 if the font does not contain a FreeType face. + + \note This function is only available on platforms that provide the FreeType library; + i.e., X11 and some Embedded Linux platforms. +*/ + +/*! + \fn QString QFont::rawName() const + + Returns the name of the font within the underlying window system. + + On X11, this function will return an empty string if Qt is built with + FontConfig support; otherwise the XLFD (X Logical Font Description) is + returned. + + Using the return value of this function is usually \e not \e + portable. + + \sa setRawName() +*/ + +/*! + \fn void QFont::setRawName(const QString &name) + + Sets a font by its system specific name. The function is + particularly useful under X, where system font settings (for + example X resources) are usually available in XLFD (X Logical Font + Description) form only. You can pass an XLFD as \a name to this + function. + + A font set with setRawName() is still a full-featured QFont. It can + be queried (for example with italic()) or modified (for example with + setItalic()) and is therefore also suitable for rendering rich text. + + If Qt's internal font database cannot resolve the raw name, the + font becomes a raw font with \a name as its family. + + Note that the present implementation does not handle wildcards in + XLFDs well, and that font aliases (file \c fonts.alias in the font + directory on X11) are not supported. + + \sa rawName(), setRawMode(), setFamily() +*/ + +/*! + \fn QString QFont::lastResortFamily() const + + Returns the "last resort" font family name. + + The current implementation tries a wide variety of common fonts, + returning the first one it finds. Is is possible that no family is + found in which case an empty string is returned. + + \sa lastResortFont() +*/ + +/*! + \fn QString QFont::defaultFamily() const + + Returns the family name that corresponds to the current style + hint. + + \sa StyleHint styleHint() setStyleHint() +*/ + +/*! + \fn QString QFont::lastResortFont() const + + Returns a "last resort" font name for the font matching algorithm. + This is used if the last resort family is not available. It will + always return a name, if necessary returning something like + "fixed" or "system". + + The current implementation tries a wide variety of common fonts, + returning the first one it finds. The implementation may change + at any time, but this function will always return a string + containing something. + + It is theoretically possible that there really isn't a + lastResortFont() in which case Qt will abort with an error + message. We have not been able to identify a case where this + happens. Please \link bughowto.html report it as a bug\endlink if + it does, preferably with a list of the fonts you have installed. + + \sa lastResortFamily() rawName() +*/ + +/*! + Constructs a font from \a font for use on the paint device \a pd. +*/ +QFont::QFont(const QFont &font, QPaintDevice *pd) + : resolve_mask(font.resolve_mask) +{ + Q_ASSERT(pd != 0); + int dpi = pd->logicalDpiY(); +#ifdef Q_WS_X11 + const QX11Info *info = qt_x11Info(pd); + int screen = info ? info->screen() : 0; +#else + const int screen = 0; +#endif + if (font.d->dpi != dpi || font.d->screen != screen ) { + d = new QFontPrivate(*font.d); + d->dpi = dpi; + d->screen = screen; + } else { + d = font.d.data(); + } +#ifdef Q_WS_WIN + if (pd->devType() == QInternal::Printer && pd->getDC()) + d->hdc = pd->getDC(); +#endif +} + +/*! + \internal +*/ +QFont::QFont(QFontPrivate *data) + : d(data), resolve_mask(QFont::AllPropertiesResolved) +{ +} + +/*! \internal + Detaches the font object from common font data. +*/ +void QFont::detach() +{ + if (d->ref == 1) { + if (d->engineData) + d->engineData->ref.deref(); + d->engineData = 0; + if (d->scFont && d->scFont != d.data()) + d->scFont->ref.deref(); + d->scFont = 0; + return; + } + + d.detach(); +} + +/*! + Constructs a font object that uses the application's default font. + + \sa QApplication::setFont(), QApplication::font() +*/ +QFont::QFont() + : d(QApplication::font().d.data()), resolve_mask(0) +{ +} + +/*! + Constructs a font object with the specified \a family, \a + pointSize, \a weight and \a italic settings. + + If \a pointSize is zero or negative, the point size of the font + is set to a system-dependent default value. Generally, this is + 12 points, except on Symbian where it is 7 points. + + The \a family name may optionally also include a foundry name, + e.g. "Helvetica [Cronyx]". If the \a family is + available from more than one foundry and the foundry isn't + specified, an arbitrary foundry is chosen. If the family isn't + available a family will be set using the \l{QFont}{font matching} + algorithm. + + \sa Weight, setFamily(), setPointSize(), setWeight(), setItalic(), + setStyleHint() QApplication::font() +*/ +QFont::QFont(const QString &family, int pointSize, int weight, bool italic) + : d(new QFontPrivate()), resolve_mask(QFont::FamilyResolved) +{ + if (pointSize <= 0) { +#ifdef Q_OS_SYMBIAN + pointSize = 7; +#else + pointSize = 12; +#endif + } else { + resolve_mask |= QFont::SizeResolved; + } + + if (weight < 0) { + weight = Normal; + } else { + resolve_mask |= QFont::WeightResolved | QFont::StyleResolved; + } + + if (italic) + resolve_mask |= QFont::StyleResolved; + + d->request.family = family; + d->request.pointSize = qreal(pointSize); + d->request.pixelSize = -1; + d->request.weight = weight; + d->request.style = italic ? QFont::StyleItalic : QFont::StyleNormal; +} + +/*! + Constructs a font that is a copy of \a font. +*/ +QFont::QFont(const QFont &font) + : d(font.d.data()), resolve_mask(font.resolve_mask) +{ +} + +/*! + Destroys the font object and frees all allocated resources. +*/ +QFont::~QFont() +{ +} + +/*! + Assigns \a font to this font and returns a reference to it. +*/ +QFont &QFont::operator=(const QFont &font) +{ + d = font.d.data(); + resolve_mask = font.resolve_mask; + return *this; +} + +/*! + Returns the requested font family name, i.e. the name set in the + constructor or the last setFont() call. + + \sa setFamily() substitutes() substitute() +*/ +QString QFont::family() const +{ + return d->request.family; +} + +/*! + Sets the family name of the font. The name is case insensitive and + may include a foundry name. + + The \a family name may optionally also include a foundry name, + e.g. "Helvetica [Cronyx]". If the \a family is + available from more than one foundry and the foundry isn't + specified, an arbitrary foundry is chosen. If the family isn't + available a family will be set using the \l{QFont}{font matching} + algorithm. + + \sa family(), setStyleHint(), QFontInfo +*/ +void QFont::setFamily(const QString &family) +{ + detach(); + + d->request.family = family; +#if defined(Q_WS_X11) + d->request.addStyle.clear(); +#endif // Q_WS_X11 + + resolve_mask |= QFont::FamilyResolved; +} + +/*! + Returns the point size of the font. Returns -1 if the font size + was specified in pixels. + + \sa setPointSize() pointSizeF() +*/ +int QFont::pointSize() const +{ + return qRound(d->request.pointSize); +} + +/*! + \since 4.8 + + \enum QFont::HintingPreference + + This enum describes the different levels of hinting that can be applied + to glyphs to improve legibility on displays where it might be warranted + by the density of pixels. + + \value PreferDefaultHinting Use the default hinting level for the target platform. + \value PreferNoHinting If possible, render text without hinting the outlines + of the glyphs. The text layout will be typographically accurate and + scalable, using the same metrics as are used e.g. when printing. + \value PreferVerticalHinting If possible, render text with no horizontal hinting, + but align glyphs to the pixel grid in the vertical direction. The text will appear + crisper on displays where the density is too low to give an accurate rendering + of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's + layout will be scalable to higher density devices (such as printers) without impacting + details such as line breaks. + \value PreferFullHinting If possible, render text with hinting in both horizontal and + vertical directions. The text will be altered to optimize legibility on the target + device, but since the metrics will depend on the target size of the text, the positions + of glyphs, line breaks, and other typographical detail will not scale, meaning that a + text layout may look different on devices with different pixel densities. + + Please note that this enum only describes a preference, as the full range of hinting levels + are not supported on all of Qt's supported platforms. The following table details the effect + of a given hinting preference on a selected set of target platforms. + + \table + \header + \o + \o PreferDefaultHinting + \o PreferNoHinting + \o PreferVerticalHinting + \o PreferFullHinting + \row + \o Windows Vista (w/o Platform Update) and earlier + \o Full hinting + \o Full hinting + \o Full hinting + \o Full hinting + \row + \o Windows 7 and Windows Vista (w/Platform Update) and DirectWrite enabled in Qt + \o Full hinting + \o Vertical hinting + \o Vertical hinting + \o Full hinting + \row + \o FreeType + \o Operating System setting + \o No hinting + \o Vertical hinting (light) + \o Full hinting + \row + \o Cocoa on Mac OS X + \o No hinting + \o No hinting + \o No hinting + \o No hinting + \endtable + + \note Please be aware that altering the hinting preference on Windows is available through + the DirectWrite font engine. This is available on Windows Vista after installing the platform + update, and on Windows 7. In order to use this extension, configure Qt using -directwrite. + The target application will then depend on the availability of DirectWrite on the target + system. + +*/ + +/*! + \since 4.8 + + Set the preference for the hinting level of the glyphs to \a hintingPreference. This is a hint + to the underlying font rendering system to use a certain level of hinting, and has varying + support across platforms. See the table in the documentation for QFont::HintingPreference for + more details. + + The default hinting preference is QFont::PreferDefaultHinting. +*/ +void QFont::setHintingPreference(HintingPreference hintingPreference) +{ + detach(); + + d->request.hintingPreference = hintingPreference; + + resolve_mask |= QFont::HintingPreferenceResolved; +} + +/*! + \since 4.8 + + Returns the currently preferred hinting level for glyphs rendered with this font. +*/ +QFont::HintingPreference QFont::hintingPreference() const +{ + return QFont::HintingPreference(d->request.hintingPreference); +} + +/*! + Sets the point size to \a pointSize. The point size must be + greater than zero. + + \sa pointSize() setPointSizeF() +*/ +void QFont::setPointSize(int pointSize) +{ + if (pointSize <= 0) { + qWarning("QFont::setPointSize: Point size <= 0 (%d), must be greater than 0", pointSize); + return; + } + + detach(); + + d->request.pointSize = qreal(pointSize); + d->request.pixelSize = -1; + + resolve_mask |= QFont::SizeResolved; +} + +/*! + Sets the point size to \a pointSize. The point size must be + greater than zero. The requested precision may not be achieved on + all platforms. + + \sa pointSizeF() setPointSize() setPixelSize() +*/ +void QFont::setPointSizeF(qreal pointSize) +{ + if (pointSize <= 0) { + qWarning("QFont::setPointSizeF: Point size <= 0 (%f), must be greater than 0", pointSize); + return; + } + + detach(); + + d->request.pointSize = pointSize; + d->request.pixelSize = -1; + + resolve_mask |= QFont::SizeResolved; +} + +/*! + Returns the point size of the font. Returns -1 if the font size was + specified in pixels. + + \sa pointSize() setPointSizeF() pixelSize() QFontInfo::pointSize() QFontInfo::pixelSize() +*/ +qreal QFont::pointSizeF() const +{ + return d->request.pointSize; +} + +/*! + Sets the font size to \a pixelSize pixels. + + Using this function makes the font device dependent. Use + setPointSize() or setPointSizeF() to set the size of the font + in a device independent manner. + + \sa pixelSize() +*/ +void QFont::setPixelSize(int pixelSize) +{ + if (pixelSize <= 0) { + qWarning("QFont::setPixelSize: Pixel size <= 0 (%d)", pixelSize); + return; + } + + detach(); + + d->request.pixelSize = pixelSize; + d->request.pointSize = -1; + + resolve_mask |= QFont::SizeResolved; +} + +/*! + Returns the pixel size of the font if it was set with + setPixelSize(). Returns -1 if the size was set with setPointSize() + or setPointSizeF(). + + \sa setPixelSize() pointSize() QFontInfo::pointSize() QFontInfo::pixelSize() +*/ +int QFont::pixelSize() const +{ + return d->request.pixelSize; +} + +#ifdef QT3_SUPPORT +/*! \obsolete + + Sets the logical pixel height of font characters when shown on + the screen to \a pixelSize. +*/ +void QFont::setPixelSizeFloat(qreal pixelSize) +{ + setPixelSize((int)pixelSize); +} +#endif + +/*! + \fn bool QFont::italic() const + + Returns true if the style() of the font is not QFont::StyleNormal + + \sa setItalic() style() +*/ + +/*! + \fn void QFont::setItalic(bool enable) + + Sets the style() of the font to QFont::StyleItalic if \a enable is true; + otherwise the style is set to QFont::StyleNormal. + + \sa italic() QFontInfo +*/ + +/*! + Returns the style of the font. + + \sa setStyle() +*/ +QFont::Style QFont::style() const +{ + return (QFont::Style)d->request.style; +} + + +/*! + Sets the style of the font to \a style. + + \sa italic(), QFontInfo +*/ +void QFont::setStyle(Style style) +{ + detach(); + + d->request.style = style; + resolve_mask |= QFont::StyleResolved; +} + +/*! + Returns the weight of the font which is one of the enumerated + values from \l{QFont::Weight}. + + \sa setWeight(), Weight, QFontInfo +*/ +int QFont::weight() const +{ + return d->request.weight; +} + +/*! + \enum QFont::Weight + + Qt uses a weighting scale from 0 to 99 similar to, but not the + same as, the scales used in Windows or CSS. A weight of 0 is + ultralight, whilst 99 will be an extremely black. + + This enum contains the predefined font weights: + + \value Light 25 + \value Normal 50 + \value DemiBold 63 + \value Bold 75 + \value Black 87 +*/ + +/*! + Sets the weight the font to \a weight, which should be a value + from the \l QFont::Weight enumeration. + + \sa weight(), QFontInfo +*/ +void QFont::setWeight(int weight) +{ + Q_ASSERT_X(weight >= 0 && weight <= 99, "QFont::setWeight", "Weight must be between 0 and 99"); + + detach(); + + d->request.weight = weight; + resolve_mask |= QFont::WeightResolved; +} + +/*! + \fn bool QFont::bold() const + + Returns true if weight() is a value greater than \link Weight + QFont::Normal \endlink; otherwise returns false. + + \sa weight(), setBold(), QFontInfo::bold() +*/ + +/*! + \fn void QFont::setBold(bool enable) + + If \a enable is true sets the font's weight to \link Weight + QFont::Bold \endlink; otherwise sets the weight to \link Weight + QFont::Normal\endlink. + + For finer boldness control use setWeight(). + + \sa bold(), setWeight() +*/ + +/*! + Returns true if underline has been set; otherwise returns false. + + \sa setUnderline() +*/ +bool QFont::underline() const +{ + return d->underline; +} + +/*! + If \a enable is true, sets underline on; otherwise sets underline + off. + + \sa underline(), QFontInfo +*/ +void QFont::setUnderline(bool enable) +{ + detach(); + + d->underline = enable; + resolve_mask |= QFont::UnderlineResolved; +} + +/*! + Returns true if overline has been set; otherwise returns false. + + \sa setOverline() +*/ +bool QFont::overline() const +{ + return d->overline; +} + +/*! + If \a enable is true, sets overline on; otherwise sets overline off. + + \sa overline(), QFontInfo +*/ +void QFont::setOverline(bool enable) +{ + detach(); + + d->overline = enable; + resolve_mask |= QFont::OverlineResolved; +} + +/*! + Returns true if strikeout has been set; otherwise returns false. + + \sa setStrikeOut() +*/ +bool QFont::strikeOut() const +{ + return d->strikeOut; +} + +/*! + If \a enable is true, sets strikeout on; otherwise sets strikeout + off. + + \sa strikeOut(), QFontInfo +*/ +void QFont::setStrikeOut(bool enable) +{ + detach(); + + d->strikeOut = enable; + resolve_mask |= QFont::StrikeOutResolved; +} + +/*! + Returns true if fixed pitch has been set; otherwise returns false. + + \sa setFixedPitch(), QFontInfo::fixedPitch() +*/ +bool QFont::fixedPitch() const +{ + return d->request.fixedPitch; +} + +/*! + If \a enable is true, sets fixed pitch on; otherwise sets fixed + pitch off. + + \sa fixedPitch(), QFontInfo +*/ +void QFont::setFixedPitch(bool enable) +{ + detach(); + + d->request.fixedPitch = enable; + d->request.ignorePitch = false; + resolve_mask |= QFont::FixedPitchResolved; +} + +/*! + Returns true if kerning should be used when drawing text with this font. + + \sa setKerning() +*/ +bool QFont::kerning() const +{ + return d->kerning; +} + +/*! + Enables kerning for this font if \a enable is true; otherwise + disables it. By default, kerning is enabled. + + When kerning is enabled, glyph metrics do not add up anymore, + even for Latin text. In other words, the assumption that + width('a') + width('b') is equal to width("ab") is not + neccesairly true. + + \sa kerning(), QFontMetrics +*/ +void QFont::setKerning(bool enable) +{ + detach(); + d->kerning = enable; + resolve_mask |= QFont::KerningResolved; +} + +/*! + Returns the StyleStrategy. + + The style strategy affects the \l{QFont}{font matching} algorithm. + See \l QFont::StyleStrategy for the list of available strategies. + + \sa setStyleHint() QFont::StyleHint +*/ +QFont::StyleStrategy QFont::styleStrategy() const +{ + return (StyleStrategy) d->request.styleStrategy; +} + +/*! + Returns the StyleHint. + + The style hint affects the \l{QFont}{font matching} algorithm. + See \l QFont::StyleHint for the list of available hints. + + \sa setStyleHint(), QFont::StyleStrategy QFontInfo::styleHint() +*/ +QFont::StyleHint QFont::styleHint() const +{ + return (StyleHint) d->request.styleHint; +} + +/*! + \enum QFont::StyleHint + + Style hints are used by the \l{QFont}{font matching} algorithm to + find an appropriate default family if a selected font family is + not available. + + \value AnyStyle leaves the font matching algorithm to choose the + family. This is the default. + + \value SansSerif the font matcher prefer sans serif fonts. + \value Helvetica is a synonym for \c SansSerif. + + \value Serif the font matcher prefers serif fonts. + \value Times is a synonym for \c Serif. + + \value TypeWriter the font matcher prefers fixed pitch fonts. + \value Courier a synonym for \c TypeWriter. + + \value OldEnglish the font matcher prefers decorative fonts. + \value Decorative is a synonym for \c OldEnglish. + + \value Monospace the font matcher prefers fonts that map to the + CSS generic font-family 'monospace'. + + \value Fantasy the font matcher prefers fonts that map to the + CSS generic font-family 'fantasy'. + + \value Cursive the font matcher prefers fonts that map to the + CSS generic font-family 'cursive'. + + \value System the font matcher prefers system fonts. +*/ + +/*! + \enum QFont::StyleStrategy + + The style strategy tells the \l{QFont}{font matching} algorithm + what type of fonts should be used to find an appropriate default + family. + + The following strategies are available: + + \value PreferDefault the default style strategy. It does not prefer + any type of font. + \value PreferBitmap prefers bitmap fonts (as opposed to outline + fonts). + \value PreferDevice prefers device fonts. + \value PreferOutline prefers outline fonts (as opposed to bitmap fonts). + \value ForceOutline forces the use of outline fonts. + \value NoAntialias don't antialias the fonts. + \value PreferAntialias antialias if possible. + \value OpenGLCompatible forces the use of OpenGL compatible + fonts. + \value NoFontMerging If the font selected for a certain writing system + does not contain a character requested to draw, then Qt automatically chooses a similar + looking font that contains the character. The NoFontMerging flag disables this feature. + Please note that enabling this flag will not prevent Qt from automatically picking a + suitable font when the selected font does not support the writing system of the text. + + Any of these may be OR-ed with one of these flags: + + \value PreferMatch prefer an exact match. The font matcher will try to + use the exact font size that has been specified. + \value PreferQuality prefer the best quality font. The font matcher + will use the nearest standard point size that the font + supports. + \value ForceIntegerMetrics forces the use of integer values in font engines that support fractional + font metrics. +*/ + +/*! + Sets the style hint and strategy to \a hint and \a strategy, + respectively. + + If these aren't set explicitly the style hint will default to + \c AnyStyle and the style strategy to \c PreferDefault. + + Qt does not support style hints on X11 since this information + is not provided by the window system. + + \sa StyleHint, styleHint(), StyleStrategy, styleStrategy(), QFontInfo +*/ +void QFont::setStyleHint(StyleHint hint, StyleStrategy strategy) +{ + detach(); + + if ((resolve_mask & (QFont::StyleHintResolved | QFont::StyleStrategyResolved)) && + (StyleHint) d->request.styleHint == hint && + (StyleStrategy) d->request.styleStrategy == strategy) + return; + + d->request.styleHint = hint; + d->request.styleStrategy = strategy; + resolve_mask |= QFont::StyleHintResolved; + resolve_mask |= QFont::StyleStrategyResolved; + +#if defined(Q_WS_X11) + d->request.addStyle.clear(); +#endif // Q_WS_X11 +} + +/*! + Sets the style strategy for the font to \a s. + + \sa QFont::StyleStrategy +*/ +void QFont::setStyleStrategy(StyleStrategy s) +{ + detach(); + + if ((resolve_mask & QFont::StyleStrategyResolved) && + s == (StyleStrategy)d->request.styleStrategy) + return; + + d->request.styleStrategy = s; + resolve_mask |= QFont::StyleStrategyResolved; +} + + +/*! + \enum QFont::Stretch + + Predefined stretch values that follow the CSS naming convention. The higher + the value, the more stretched the text is. + + \value UltraCondensed 50 + \value ExtraCondensed 62 + \value Condensed 75 + \value SemiCondensed 87 + \value Unstretched 100 + \value SemiExpanded 112 + \value Expanded 125 + \value ExtraExpanded 150 + \value UltraExpanded 200 + + \sa setStretch() stretch() +*/ + +/*! + Returns the stretch factor for the font. + + \sa setStretch() + */ +int QFont::stretch() const +{ + return d->request.stretch; +} + +/*! + Sets the stretch factor for the font. + + The stretch factor changes the width of all characters in the font + by \a factor percent. For example, setting \a factor to 150 + results in all characters in the font being 1.5 times (ie. 150%) + wider. The default stretch factor is 100. The minimum stretch + factor is 1, and the maximum stretch factor is 4000. + + The stretch factor is only applied to outline fonts. The stretch + factor is ignored for bitmap fonts. + + NOTE: QFont cannot stretch XLFD fonts. When loading XLFD fonts on + X11, the stretch factor is matched against a predefined set of + values for the SETWIDTH_NAME field of the XLFD. + + \sa stretch() QFont::Stretch +*/ +void QFont::setStretch(int factor) +{ + if (factor < 1 || factor > 4000) { + qWarning("QFont::setStretch: Parameter '%d' out of range", factor); + return; + } + + if ((resolve_mask & QFont::StretchResolved) && + d->request.stretch == (uint)factor) + return; + + detach(); + + d->request.stretch = (uint)factor; + resolve_mask |= QFont::StretchResolved; +} + +/*! + \enum QFont::SpacingType + \since 4.4 + + \value PercentageSpacing A value of 100 will keep the spacing unchanged; a value of 200 will enlarge the + spacing after a character by the width of the character itself. + \value AbsoluteSpacing A positive value increases the letter spacing by the corresponding pixels; a negative + value decreases the spacing. +*/ + +/*! + \since 4.4 + Returns the letter spacing for the font. + + \sa setLetterSpacing(), letterSpacingType(), setWordSpacing() + */ +qreal QFont::letterSpacing() const +{ + return d->letterSpacing.toReal(); +} + +/*! + \since 4.4 + Sets the letter spacing for the font to \a spacing and the type + of spacing to \a type. + + Letter spacing changes the default spacing between individual + letters in the font. The spacing between the letters can be + made smaller as well as larger. + + \sa letterSpacing(), letterSpacingType(), setWordSpacing() +*/ +void QFont::setLetterSpacing(SpacingType type, qreal spacing) +{ + const QFixed newSpacing = QFixed::fromReal(spacing); + const bool absoluteSpacing = type == AbsoluteSpacing; + if ((resolve_mask & QFont::LetterSpacingResolved) && + d->letterSpacingIsAbsolute == absoluteSpacing && + d->letterSpacing == newSpacing) + return; + + detach(); + + d->letterSpacing = newSpacing; + d->letterSpacingIsAbsolute = absoluteSpacing; + resolve_mask |= QFont::LetterSpacingResolved; +} + +/*! + \since 4.4 + Returns the spacing type used for letter spacing. + + \sa letterSpacing(), setLetterSpacing(), setWordSpacing() +*/ +QFont::SpacingType QFont::letterSpacingType() const +{ + return d->letterSpacingIsAbsolute ? AbsoluteSpacing : PercentageSpacing; +} + +/*! + \since 4.4 + Returns the word spacing for the font. + + \sa setWordSpacing(), setLetterSpacing() + */ +qreal QFont::wordSpacing() const +{ + return d->wordSpacing.toReal(); +} + +/*! + \since 4.4 + Sets the word spacing for the font to \a spacing. + + Word spacing changes the default spacing between individual + words. A positive value increases the word spacing + by a corresponding amount of pixels, while a negative value + decreases the inter-word spacing accordingly. + + Word spacing will not apply to writing systems, where indiviaul + words are not separated by white space. + + \sa wordSpacing(), setLetterSpacing() +*/ +void QFont::setWordSpacing(qreal spacing) +{ + const QFixed newSpacing = QFixed::fromReal(spacing); + if ((resolve_mask & QFont::WordSpacingResolved) && + d->wordSpacing == newSpacing) + return; + + detach(); + + d->wordSpacing = newSpacing; + resolve_mask |= QFont::WordSpacingResolved; +} + +/*! + \enum QFont::Capitalization + \since 4.4 + + Rendering option for text this font applies to. + + + \value MixedCase This is the normal text rendering option where no capitalization change is applied. + \value AllUppercase This alters the text to be rendered in all uppercase type. + \value AllLowercase This alters the text to be rendered in all lowercase type. + \value SmallCaps This alters the text to be rendered in small-caps type. + \value Capitalize This alters the text to be rendered with the first character of each word as an uppercase character. +*/ + +/*! + \since 4.4 + Sets the capitalization of the text in this font to \a caps. + + A font's capitalization makes the text appear in the selected capitalization mode. + + \sa capitalization() +*/ +void QFont::setCapitalization(Capitalization caps) +{ + if ((resolve_mask & QFont::CapitalizationResolved) && + capitalization() == caps) + return; + + detach(); + + d->capital = caps; + resolve_mask |= QFont::CapitalizationResolved; +} + +/*! + \since 4.4 + Returns the current capitalization type of the font. + + \sa setCapitalization() +*/ +QFont::Capitalization QFont::capitalization() const +{ + return static_cast (d->capital); +} + + +/*! + If \a enable is true, turns raw mode on; otherwise turns raw mode + off. This function only has an effect under X11. + + If raw mode is enabled, Qt will search for an X font with a + complete font name matching the family name, ignoring all other + values set for the QFont. If the font name matches several fonts, + Qt will use the first font returned by X. QFontInfo \e cannot be + used to fetch information about a QFont using raw mode (it will + return the values set in the QFont for all parameters, including + the family name). + + \warning Do not use raw mode unless you really, really need it! In + most (if not all) cases, setRawName() is a much better choice. + + \sa rawMode(), setRawName() +*/ +void QFont::setRawMode(bool enable) +{ + detach(); + + if ((bool) d->rawMode == enable) return; + + d->rawMode = enable; +} + +/*! + Returns true if a window system font exactly matching the settings + of this font is available. + + \sa QFontInfo +*/ +bool QFont::exactMatch() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return (d->rawMode + ? engine->type() != QFontEngine::Box + : d->request.exactMatch(engine->fontDef)); +} + +/*! + Returns true if this font is equal to \a f; otherwise returns + false. + + Two QFonts are considered equal if their font attributes are + equal. If rawMode() is enabled for both fonts, only the family + fields are compared. + + \sa operator!=() isCopyOf() +*/ +bool QFont::operator==(const QFont &f) const +{ + return (f.d == d + || (f.d->request == d->request + && f.d->request.pointSize == d->request.pointSize + && f.d->underline == d->underline + && f.d->overline == d->overline + && f.d->strikeOut == d->strikeOut + && f.d->kerning == d->kerning + && f.d->capital == d->capital + && f.d->letterSpacingIsAbsolute == d->letterSpacingIsAbsolute + && f.d->letterSpacing == d->letterSpacing + && f.d->wordSpacing == d->wordSpacing + )); +} + + +/*! + Provides an arbitrary comparison of this font and font \a f. + All that is guaranteed is that the operator returns false if both + fonts are equal and that (f1 \< f2) == !(f2 \< f1) if the fonts + are not equal. + + This function is useful in some circumstances, for example if you + want to use QFont objects as keys in a QMap. + + \sa operator==() operator!=() isCopyOf() +*/ +bool QFont::operator<(const QFont &f) const +{ + if (f.d == d) return false; + // the < operator for fontdefs ignores point sizes. + QFontDef &r1 = f.d->request; + QFontDef &r2 = d->request; + if (r1.pointSize != r2.pointSize) return r1.pointSize < r2.pointSize; + if (r1.pixelSize != r2.pixelSize) return r1.pixelSize < r2.pixelSize; + if (r1.weight != r2.weight) return r1.weight < r2.weight; + if (r1.style != r2.style) return r1.style < r2.style; + if (r1.stretch != r2.stretch) return r1.stretch < r2.stretch; + if (r1.styleHint != r2.styleHint) return r1.styleHint < r2.styleHint; + if (r1.styleStrategy != r2.styleStrategy) return r1.styleStrategy < r2.styleStrategy; + if (r1.family != r2.family) return r1.family < r2.family; +#ifdef Q_WS_X11 + if (r1.addStyle != r2.addStyle) return r1.addStyle < r2.addStyle; +#endif // Q_WS_X11 + if (f.d->capital != d->capital) return f.d->capital < d->capital; + + if (f.d->letterSpacingIsAbsolute != d->letterSpacingIsAbsolute) return f.d->letterSpacingIsAbsolute < d->letterSpacingIsAbsolute; + if (f.d->letterSpacing != d->letterSpacing) return f.d->letterSpacing < d->letterSpacing; + if (f.d->wordSpacing != d->wordSpacing) return f.d->wordSpacing < d->wordSpacing; + + int f1attrs = (f.d->underline << 3) + (f.d->overline << 2) + (f.d->strikeOut<<1) + f.d->kerning; + int f2attrs = (d->underline << 3) + (d->overline << 2) + (d->strikeOut<<1) + d->kerning; + return f1attrs < f2attrs; +} + + +/*! + Returns true if this font is different from \a f; otherwise + returns false. + + Two QFonts are considered to be different if their font attributes + are different. If rawMode() is enabled for both fonts, only the + family fields are compared. + + \sa operator==() +*/ +bool QFont::operator!=(const QFont &f) const +{ + return !(operator==(f)); +} + +/*! + Returns the font as a QVariant +*/ +QFont::operator QVariant() const +{ + return QVariant(QVariant::Font, this); +} + +/*! + Returns true if this font and \a f are copies of each other, i.e. + one of them was created as a copy of the other and neither has + been modified since. This is much stricter than equality. + + \sa operator=() operator==() +*/ +bool QFont::isCopyOf(const QFont & f) const +{ + return d == f.d; +} + +/*! + Returns true if raw mode is used for font name matching; otherwise + returns false. + + \sa setRawMode() rawName() +*/ +bool QFont::rawMode() const +{ + return d->rawMode; +} + +/*! + Returns a new QFont that has attributes copied from \a other that + have not been previously set on this font. +*/ +QFont QFont::resolve(const QFont &other) const +{ + if (*this == other + && (resolve_mask == other.resolve_mask || resolve_mask == 0) + && d->dpi == other.d->dpi) { + QFont o = other; + o.resolve_mask = resolve_mask; + return o; + } + + QFont font(*this); + font.detach(); + font.d->resolve(resolve_mask, other.d.data()); + + return font; +} + +/*! + \fn uint QFont::resolve() const + \internal +*/ + +/*! + \fn void QFont::resolve(uint mask) + \internal +*/ + +#ifdef QT3_SUPPORT + +/*! \obsolete + + Please use QApplication::font() instead. +*/ +QFont QFont::defaultFont() +{ + return QApplication::font(); +} + +/*! \obsolete + + Please use QApplication::setFont() instead. +*/ +void QFont::setDefaultFont(const QFont &f) +{ + QApplication::setFont(f); +} + +/*! + \fn qreal QFont::pointSizeFloat() const + \compat + + Use pointSizeF() instead. +*/ + +/*! + \fn void QFont::setPointSizeFloat(qreal size) + \compat + + Use setPointSizeF() instead. +*/ +#endif + + + + +/***************************************************************************** + QFont substitution management + *****************************************************************************/ + +typedef QHash QFontSubst; +Q_GLOBAL_STATIC(QFontSubst, globalFontSubst) + +// create substitution dict +static void initFontSubst() +{ + // default substitutions + static const char * const initTbl[] = { + +#if defined(Q_WS_X11) + "arial", "helvetica", + "times new roman", "times", + "courier new", "courier", + "sans serif", "helvetica", +#elif defined(Q_WS_WIN) + "times", "times new roman", + "courier", "courier new", + "helvetica", "arial", + "sans serif", "arial", +#endif + + 0, 0 + }; + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + if (!fontSubst->isEmpty()) + return; +#if defined(Q_WS_X11) && !defined(QT_NO_FONTCONFIG) + if (X11->has_fontconfig) + return; +#endif + + for (int i=0; initTbl[i] != 0; i += 2) { + QStringList &list = (*fontSubst)[QString::fromLatin1(initTbl[i])]; + list.append(QString::fromLatin1(initTbl[i+1])); + } +} + +/*! + Returns the first family name to be used whenever \a familyName is + specified. The lookup is case insensitive. + + If there is no substitution for \a familyName, \a familyName is + returned. + + To obtain a list of substitutions use substitutes(). + + \sa setFamily() insertSubstitutions() insertSubstitution() removeSubstitution() +*/ +QString QFont::substitute(const QString &familyName) +{ + initFontSubst(); + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + QFontSubst::ConstIterator it = fontSubst->constFind(familyName.toLower()); + if (it != fontSubst->constEnd() && !(*it).isEmpty()) + return (*it).first(); + + return familyName; +} + + +/*! + Returns a list of family names to be used whenever \a familyName + is specified. The lookup is case insensitive. + + If there is no substitution for \a familyName, an empty list is + returned. + + \sa substitute() insertSubstitutions() insertSubstitution() removeSubstitution() + */ +QStringList QFont::substitutes(const QString &familyName) +{ + initFontSubst(); + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + return fontSubst->value(familyName.toLower(), QStringList()); +} + + +/*! + Inserts \a substituteName into the substitution + table for the family \a familyName. + + \sa insertSubstitutions() removeSubstitution() substitutions() substitute() substitutes() +*/ +void QFont::insertSubstitution(const QString &familyName, + const QString &substituteName) +{ + initFontSubst(); + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + QStringList &list = (*fontSubst)[familyName.toLower()]; + QString s = substituteName.toLower(); + if (!list.contains(s)) + list.append(s); +} + + +/*! + Inserts the list of families \a substituteNames into the + substitution list for \a familyName. + + \sa insertSubstitution(), removeSubstitution(), substitutions(), substitute() +*/ +void QFont::insertSubstitutions(const QString &familyName, + const QStringList &substituteNames) +{ + initFontSubst(); + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + QStringList &list = (*fontSubst)[familyName.toLower()]; + QStringList::ConstIterator it = substituteNames.constBegin(); + while (it != substituteNames.constEnd()) { + QString s = (*it).toLower(); + if (!list.contains(s)) + list.append(s); + it++; + } +} + +/*! \fn void QFont::initialize() + \internal + + Internal function that initializes the font system. The font cache + and font dict do not alloc the keys. The key is a QString which is + shared between QFontPrivate and QXFontName. +*/ + +/*! \fn void QFont::cleanup() + \internal + + Internal function that cleans up the font system. +*/ + +// ### mark: should be called removeSubstitutions() +/*! + Removes all the substitutions for \a familyName. + + \sa insertSubstitutions(), insertSubstitution(), substitutions(), substitute() +*/ +void QFont::removeSubstitution(const QString &familyName) +{ // ### function name should be removeSubstitutions() or + // ### removeSubstitutionList() + initFontSubst(); + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + fontSubst->remove(familyName.toLower()); +} + + +/*! + Returns a sorted list of substituted family names. + + \sa insertSubstitution(), removeSubstitution(), substitute() +*/ +QStringList QFont::substitutions() +{ + initFontSubst(); + + QFontSubst *fontSubst = globalFontSubst(); + Q_ASSERT(fontSubst != 0); + QStringList ret; + QFontSubst::ConstIterator it = fontSubst->constBegin(); + + while (it != fontSubst->constEnd()) { + ret.append(it.key()); + ++it; + } + + ret.sort(); + return ret; +} + + +/* \internal + Internal function. Converts boolean font settings to an unsigned + 8-bit number. Used for serialization etc. +*/ +static quint8 get_font_bits(int version, const QFontPrivate *f) +{ + Q_ASSERT(f != 0); + quint8 bits = 0; + if (f->request.style) + bits |= 0x01; + if (f->underline) + bits |= 0x02; + if (f->overline) + bits |= 0x40; + if (f->strikeOut) + bits |= 0x04; + if (f->request.fixedPitch) + bits |= 0x08; + // if (f.hintSetByUser) + // bits |= 0x10; + if (f->rawMode) + bits |= 0x20; + if (version >= QDataStream::Qt_4_0) { + if (f->kerning) + bits |= 0x10; + } + if (f->request.style == QFont::StyleOblique) + bits |= 0x80; + return bits; +} + +static quint8 get_extended_font_bits(const QFontPrivate *f) +{ + Q_ASSERT(f != 0); + quint8 bits = 0; + if (f->request.ignorePitch) + bits |= 0x01; + if (f->letterSpacingIsAbsolute) + bits |= 0x02; + return bits; +} + +#ifndef QT_NO_DATASTREAM + +/* \internal + Internal function. Sets boolean font settings from an unsigned + 8-bit number. Used for serialization etc. +*/ +static void set_font_bits(int version, quint8 bits, QFontPrivate *f) +{ + Q_ASSERT(f != 0); + f->request.style = (bits & 0x01) != 0 ? QFont::StyleItalic : QFont::StyleNormal; + f->underline = (bits & 0x02) != 0; + f->overline = (bits & 0x40) != 0; + f->strikeOut = (bits & 0x04) != 0; + f->request.fixedPitch = (bits & 0x08) != 0; + // f->hintSetByUser = (bits & 0x10) != 0; + f->rawMode = (bits & 0x20) != 0; + if (version >= QDataStream::Qt_4_0) + f->kerning = (bits & 0x10) != 0; + if ((bits & 0x80) != 0) + f->request.style = QFont::StyleOblique; +} + +static void set_extended_font_bits(quint8 bits, QFontPrivate *f) +{ + Q_ASSERT(f != 0); + f->request.ignorePitch = (bits & 0x01) != 0; + f->letterSpacingIsAbsolute = (bits & 0x02) != 0; +} +#endif + + +/*! + Returns the font's key, a textual representation of a font. It is + typically used as the key for a cache or dictionary of fonts. + + \sa QMap +*/ +QString QFont::key() const +{ + return toString(); +} + +/*! + Returns a description of the font. The description is a + comma-separated list of the attributes, perfectly suited for use + in QSettings. + + \sa fromString() + */ +QString QFont::toString() const +{ + const QChar comma(QLatin1Char(',')); + return family() + comma + + QString::number( pointSizeF()) + comma + + QString::number( pixelSize()) + comma + + QString::number((int) styleHint()) + comma + + QString::number( weight()) + comma + + QString::number((int) style()) + comma + + QString::number((int) underline()) + comma + + QString::number((int) strikeOut()) + comma + + QString::number((int)fixedPitch()) + comma + + QString::number((int) rawMode()); +} + + +/*! + Sets this font to match the description \a descrip. The description + is a comma-separated list of the font attributes, as returned by + toString(). + + \sa toString() + */ +bool QFont::fromString(const QString &descrip) +{ + QStringList l(descrip.split(QLatin1Char(','))); + + int count = l.count(); + if (!count || (count > 2 && count < 9) || count > 11) { + qWarning("QFont::fromString: Invalid description '%s'", + descrip.isEmpty() ? "(empty)" : descrip.toLatin1().data()); + return false; + } + + setFamily(l[0]); + if (count > 1 && l[1].toDouble() > 0.0) + setPointSizeF(l[1].toDouble()); + if (count == 9) { + setStyleHint((StyleHint) l[2].toInt()); + setWeight(qMax(qMin(99, l[3].toInt()), 0)); + setItalic(l[4].toInt()); + setUnderline(l[5].toInt()); + setStrikeOut(l[6].toInt()); + setFixedPitch(l[7].toInt()); + setRawMode(l[8].toInt()); + } else if (count == 10) { + if (l[2].toInt() > 0) + setPixelSize(l[2].toInt()); + setStyleHint((StyleHint) l[3].toInt()); + setWeight(qMax(qMin(99, l[4].toInt()), 0)); + setStyle((QFont::Style)l[5].toInt()); + setUnderline(l[6].toInt()); + setStrikeOut(l[7].toInt()); + setFixedPitch(l[8].toInt()); + setRawMode(l[9].toInt()); + } + if (count >= 9 && !d->request.fixedPitch) // assume 'false' fixedPitch equals default + d->request.ignorePitch = true; + + return true; +} + +#if !defined(Q_WS_QWS) +/*! \internal + + Internal function that dumps font cache statistics. +*/ +void QFont::cacheStatistics() +{ + + +} +#endif // !Q_WS_QWS + + + +/***************************************************************************** + QFont stream functions + *****************************************************************************/ +#ifndef QT_NO_DATASTREAM + +/*! + \relates QFont + + Writes the font \a font to the data stream \a s. (toString() + writes to a text stream.) + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ +QDataStream &operator<<(QDataStream &s, const QFont &font) +{ + if (s.version() == 1) { + s << font.d->request.family.toLatin1(); + } else { + s << font.d->request.family; + } + + if (s.version() >= QDataStream::Qt_4_0) { + // 4.0 + double pointSize = font.d->request.pointSize; + qint32 pixelSize = font.d->request.pixelSize; + s << pointSize; + s << pixelSize; + } else if (s.version() <= 3) { + qint16 pointSize = (qint16) (font.d->request.pointSize * 10); + if (pointSize < 0) { +#ifdef Q_WS_X11 + pointSize = (qint16)(font.d->request.pixelSize*720/QX11Info::appDpiY()); +#else + pointSize = (qint16)QFontInfo(font).pointSize() * 10; +#endif + } + s << pointSize; + } else { + s << (qint16) (font.d->request.pointSize * 10); + s << (qint16) font.d->request.pixelSize; + } + + s << (quint8) font.d->request.styleHint; + if (s.version() >= QDataStream::Qt_3_1) + s << (quint8) font.d->request.styleStrategy; + s << (quint8) 0 + << (quint8) font.d->request.weight + << get_font_bits(s.version(), font.d.data()); + if (s.version() >= QDataStream::Qt_4_3) + s << (quint16)font.d->request.stretch; + if (s.version() >= QDataStream::Qt_4_4) + s << get_extended_font_bits(font.d.data()); + if (s.version() >= QDataStream::Qt_4_5) { + s << font.d->letterSpacing.value(); + s << font.d->wordSpacing.value(); + } + return s; +} + + +/*! + \relates QFont + + Reads the font \a font from the data stream \a s. (fromString() + reads from a text stream.) + + \sa \link datastreamformat.html Format of the QDataStream operators \endlink +*/ +QDataStream &operator>>(QDataStream &s, QFont &font) +{ + font.d = new QFontPrivate; + font.resolve_mask = QFont::AllPropertiesResolved; + + quint8 styleHint, styleStrategy = QFont::PreferDefault, charSet, weight, bits; + + if (s.version() == 1) { + QByteArray fam; + s >> fam; + font.d->request.family = QString::fromLatin1(fam); + } else { + s >> font.d->request.family; + } + + if (s.version() >= QDataStream::Qt_4_0) { + // 4.0 + double pointSize; + qint32 pixelSize; + s >> pointSize; + s >> pixelSize; + font.d->request.pointSize = qreal(pointSize); + font.d->request.pixelSize = pixelSize; + } else { + qint16 pointSize, pixelSize = -1; + s >> pointSize; + if (s.version() >= 4) + s >> pixelSize; + font.d->request.pointSize = qreal(pointSize / 10.); + font.d->request.pixelSize = pixelSize; + } + s >> styleHint; + if (s.version() >= QDataStream::Qt_3_1) + s >> styleStrategy; + + s >> charSet; + s >> weight; + s >> bits; + + font.d->request.styleHint = styleHint; + font.d->request.styleStrategy = styleStrategy; + font.d->request.weight = weight; + + set_font_bits(s.version(), bits, font.d.data()); + + if (s.version() >= QDataStream::Qt_4_3) { + quint16 stretch; + s >> stretch; + font.d->request.stretch = stretch; + } + + if (s.version() >= QDataStream::Qt_4_4) { + quint8 extendedBits; + s >> extendedBits; + set_extended_font_bits(extendedBits, font.d.data()); + } + if (s.version() >= QDataStream::Qt_4_5) { + int value; + s >> value; + font.d->letterSpacing.setValue(value); + s >> value; + font.d->wordSpacing.setValue(value); + } + + return s; +} + +#endif // QT_NO_DATASTREAM + + +/***************************************************************************** + QFontInfo member functions + *****************************************************************************/ + +/*! + \class QFontInfo + \reentrant + + \brief The QFontInfo class provides general information about fonts. + + \ingroup appearance + \ingroup shared + + The QFontInfo class provides the same access functions as QFont, + e.g. family(), pointSize(), italic(), weight(), fixedPitch(), + styleHint() etc. But whilst the QFont access functions return the + values that were set, a QFontInfo object returns the values that + apply to the font that will actually be used to draw the text. + + For example, when the program asks for a 25pt Courier font on a + machine that has a non-scalable 24pt Courier font, QFont will + (normally) use the 24pt Courier for rendering. In this case, + QFont::pointSize() returns 25 and QFontInfo::pointSize() returns + 24. + + There are three ways to create a QFontInfo object. + \list 1 + \o Calling the QFontInfo constructor with a QFont creates a font + info object for a screen-compatible font, i.e. the font cannot be + a printer font. If the font is changed later, the font + info object is \e not updated. + + (Note: If you use a printer font the values returned may be + inaccurate. Printer fonts are not always accessible so the nearest + screen font is used if a printer font is supplied.) + + \o QWidget::fontInfo() returns the font info for a widget's font. + This is equivalent to calling QFontInfo(widget->font()). If the + widget's font is changed later, the font info object is \e not + updated. + + \o QPainter::fontInfo() returns the font info for a painter's + current font. If the painter's font is changed later, the font + info object is \e not updated. + \endlist + + \sa QFont QFontMetrics QFontDatabase +*/ + +/*! + Constructs a font info object for \a font. + + The font must be screen-compatible, i.e. a font you use when + drawing text in \link QWidget widgets\endlink or \link QPixmap + pixmaps\endlink, not QPicture or QPrinter. + + The font info object holds the information for the font that is + passed in the constructor at the time it is created, and is not + updated if the font's attributes are changed later. + + Use QPainter::fontInfo() to get the font info when painting. + This will give correct results also when painting on paint device + that is not screen-compatible. +*/ +QFontInfo::QFontInfo(const QFont &font) + : d(font.d.data()) +{ +} + +/*! + Constructs a copy of \a fi. +*/ +QFontInfo::QFontInfo(const QFontInfo &fi) + : d(fi.d.data()) +{ +} + +/*! + Destroys the font info object. +*/ +QFontInfo::~QFontInfo() +{ +} + +/*! + Assigns the font info in \a fi. +*/ +QFontInfo &QFontInfo::operator=(const QFontInfo &fi) +{ + d = fi.d.data(); + return *this; +} + +/*! + Returns the family name of the matched window system font. + + \sa QFont::family() +*/ +QString QFontInfo::family() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->fontDef.family; +} + +/*! + Returns the point size of the matched window system font. + + \sa pointSizeF() QFont::pointSize() +*/ +int QFontInfo::pointSize() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->fontDef.pointSize); +} + +/*! + Returns the point size of the matched window system font. + + \sa QFont::pointSizeF() +*/ +qreal QFontInfo::pointSizeF() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->fontDef.pointSize; +} + +/*! + Returns the pixel size of the matched window system font. + + \sa QFont::pointSize() +*/ +int QFontInfo::pixelSize() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->fontDef.pixelSize; +} + +/*! + Returns the italic value of the matched window system font. + + \sa QFont::italic() +*/ +bool QFontInfo::italic() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->fontDef.style != QFont::StyleNormal; +} + +/*! + Returns the style value of the matched window system font. + + \sa QFont::style() +*/ +QFont::Style QFontInfo::style() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return (QFont::Style)engine->fontDef.style; +} + +/*! + Returns the weight of the matched window system font. + + \sa QFont::weight(), bold() +*/ +int QFontInfo::weight() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->fontDef.weight; + +} + +/*! + \fn bool QFontInfo::bold() const + + Returns true if weight() would return a value greater than + QFont::Normal; otherwise returns false. + + \sa weight(), QFont::bold() +*/ + +/*! + Returns the underline value of the matched window system font. + + \sa QFont::underline() + + \internal + + Here we read the underline flag directly from the QFont. + This is OK for X11 and for Windows because we always get what we want. +*/ +bool QFontInfo::underline() const +{ + return d->underline; +} + +/*! + Returns the overline value of the matched window system font. + + \sa QFont::overline() + + \internal + + Here we read the overline flag directly from the QFont. + This is OK for X11 and for Windows because we always get what we want. +*/ +bool QFontInfo::overline() const +{ + return d->overline; +} + +/*! + Returns the strikeout value of the matched window system font. + + \sa QFont::strikeOut() + + \internal Here we read the strikeOut flag directly from the QFont. + This is OK for X11 and for Windows because we always get what we want. +*/ +bool QFontInfo::strikeOut() const +{ + return d->strikeOut; +} + +/*! + Returns the fixed pitch value of the matched window system font. + + \sa QFont::fixedPitch() +*/ +bool QFontInfo::fixedPitch() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); +#ifdef Q_OS_MAC + if (!engine->fontDef.fixedPitchComputed) { + QChar ch[2] = { QLatin1Char('i'), QLatin1Char('m') }; + QGlyphLayoutArray<2> g; + int l = 2; + engine->stringToCMap(ch, 2, &g, &l, 0); + engine->fontDef.fixedPitch = g.advances_x[0] == g.advances_x[1]; + engine->fontDef.fixedPitchComputed = true; + } +#endif + return engine->fontDef.fixedPitch; +} + +/*! + Returns the style of the matched window system font. + + Currently only returns the style hint set in QFont. + + \sa QFont::styleHint() QFont::StyleHint +*/ +QFont::StyleHint QFontInfo::styleHint() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return (QFont::StyleHint) engine->fontDef.styleHint; +} + +/*! + Returns true if the font is a raw mode font; otherwise returns + false. + + If it is a raw mode font, all other functions in QFontInfo will + return the same values set in the QFont, regardless of the font + actually used. + + \sa QFont::rawMode() +*/ +bool QFontInfo::rawMode() const +{ + return d->rawMode; +} + +/*! + Returns true if the matched window system font is exactly the same + as the one specified by the font; otherwise returns false. + + \sa QFont::exactMatch() +*/ +bool QFontInfo::exactMatch() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return (d->rawMode + ? engine->type() != QFontEngine::Box + : d->request.exactMatch(engine->fontDef)); +} + + + + +// ********************************************************************** +// QFontCache +// ********************************************************************** + +#ifdef QFONTCACHE_DEBUG +// fast timeouts for debugging +static const int fast_timeout = 1000; // 1s +static const int slow_timeout = 5000; // 5s +#else +static const int fast_timeout = 10000; // 10s +static const int slow_timeout = 300000; // 5m +#endif // QFONTCACHE_DEBUG + +const uint QFontCache::min_cost = 4*1024; // 4mb + +#ifdef QT_NO_THREAD +Q_GLOBAL_STATIC(QFontCache, theFontCache) + +QFontCache *QFontCache::instance() +{ + return theFontCache(); +} + +void QFontCache::cleanup() +{ +} +#else +Q_GLOBAL_STATIC(QThreadStorage, theFontCache) + +QFontCache *QFontCache::instance() +{ + QFontCache *&fontCache = theFontCache()->localData(); + if (!fontCache) + fontCache = new QFontCache; + return fontCache; +} + +void QFontCache::cleanup() +{ + QThreadStorage *cache = 0; + QT_TRY { + cache = theFontCache(); + } QT_CATCH (const std::bad_alloc &) { + // no cache - just ignore + } + if (cache && cache->hasLocalData()) + cache->setLocalData(0); +} +#endif // QT_NO_THREAD + +QFontCache::QFontCache() + : QObject(), total_cost(0), max_cost(min_cost), + current_timestamp(0), fast(false), timer_id(-1) +{ +} + +QFontCache::~QFontCache() +{ + clear(); + { + EngineDataCache::ConstIterator it = engineDataCache.constBegin(), + end = engineDataCache.constEnd(); + while (it != end) { + if (it.value()->ref == 0) + delete it.value(); + else + FC_DEBUG("QFontCache::~QFontCache: engineData %p still has refcount %d", + it.value(), int(it.value()->ref)); + ++it; + } + } + EngineCache::ConstIterator it = engineCache.constBegin(), + end = engineCache.constEnd(); + while (it != end) { + if (--it.value().data->cache_count == 0) { + if (it.value().data->ref == 0) { + FC_DEBUG("QFontCache::~QFontCache: deleting engine %p key=(%d / %g %g %d %d %d)", + it.value().data, it.key().script, it.key().def.pointSize, + it.key().def.pixelSize, it.key().def.weight, it.key().def.style, + it.key().def.fixedPitch); + + delete it.value().data; + } else { + FC_DEBUG("QFontCache::~QFontCache: engine = %p still has refcount %d", + it.value().data, int(it.value().data->ref)); + } + } + ++it; + } +} + +void QFontCache::clear() +{ + { + EngineDataCache::Iterator it = engineDataCache.begin(), + end = engineDataCache.end(); + while (it != end) { + QFontEngineData *data = it.value(); +#if !defined(Q_WS_MAC) + for (int i = 0; i < QUnicodeTables::ScriptCount; ++i) { + if (data->engines[i]) { + data->engines[i]->ref.deref(); + data->engines[i] = 0; + } + } +#else + if (data->engine) { + data->engine->ref.deref(); + data->engine = 0; + } +#endif + ++it; + } + } + + for (EngineCache::Iterator it = engineCache.begin(), end = engineCache.end(); + it != end; ++it) { + if (it->data->ref == 0) { + delete it->data; + it->data = 0; + } + } + + for (EngineCache::Iterator it = engineCache.begin(), end = engineCache.end(); + it != end; ++it) { + if (it->data && it->data->ref == 0) { + delete it->data; + it->data = 0; + } + } + + engineCache.clear(); +} + +#if defined(Q_WS_QWS) && !defined(QT_NO_QWS_QPF2) +void QFontCache::removeEngineForFont(const QByteArray &_fontName) +{ + + /* This could be optimized but the code becomes much more complex if we want to handle multi + * font engines and it is probably not worth it. Therefore we just clear the entire font cache. + */ + Q_UNUSED(_fontName); + clear(); +} +#endif + +QFontEngineData *QFontCache::findEngineData(const Key &key) const +{ + EngineDataCache::ConstIterator it = engineDataCache.find(key), + end = engineDataCache.end(); + if (it == end) return 0; + + // found + return it.value(); +} + +void QFontCache::insertEngineData(const Key &key, QFontEngineData *engineData) +{ + FC_DEBUG("QFontCache: inserting new engine data %p", engineData); + + engineDataCache.insert(key, engineData); + increaseCost(sizeof(QFontEngineData)); +} + +QFontEngine *QFontCache::findEngine(const Key &key) +{ + EngineCache::Iterator it = engineCache.find(key), + end = engineCache.end(); + if (it == end) return 0; + + // found... update the hitcount and timestamp + it.value().hits++; + it.value().timestamp = ++current_timestamp; + + FC_DEBUG("QFontCache: found font engine\n" + " %p: timestamp %4u hits %3u ref %2d/%2d, type '%s'", + it.value().data, it.value().timestamp, it.value().hits, + int(it.value().data->ref), it.value().data->cache_count, + it.value().data->name()); + + return it.value().data; +} + +void QFontCache::insertEngine(const Key &key, QFontEngine *engine) +{ + FC_DEBUG("QFontCache: inserting new engine %p", engine); + + Engine data(engine); + data.timestamp = ++current_timestamp; + + engineCache.insert(key, data); + + // only increase the cost if this is the first time we insert the engine + if (engine->cache_count == 0) + increaseCost(engine->cache_cost); + + ++engine->cache_count; +} + +void QFontCache::increaseCost(uint cost) +{ + cost = (cost + 512) / 1024; // store cost in kb + cost = cost > 0 ? cost : 1; + total_cost += cost; + + FC_DEBUG(" COST: increased %u kb, total_cost %u kb, max_cost %u kb", + cost, total_cost, max_cost); + + if (total_cost > max_cost) { + max_cost = total_cost; + + if (timer_id == -1 || ! fast) { + FC_DEBUG(" TIMER: starting fast timer (%d ms)", fast_timeout); + + if (timer_id != -1) killTimer(timer_id); + timer_id = startTimer(fast_timeout); + fast = true; + } + } +} + +void QFontCache::decreaseCost(uint cost) +{ + cost = (cost + 512) / 1024; // cost is stored in kb + cost = cost > 0 ? cost : 1; + Q_ASSERT(cost <= total_cost); + total_cost -= cost; + + FC_DEBUG(" COST: decreased %u kb, total_cost %u kb, max_cost %u kb", + cost, total_cost, max_cost); +} + +#if defined(Q_WS_WIN) || defined (Q_WS_QWS) +void QFontCache::cleanupPrinterFonts() +{ + FC_DEBUG("QFontCache::cleanupPrinterFonts"); + + { + FC_DEBUG(" CLEAN engine data:"); + + // clean out all unused engine data + EngineDataCache::Iterator it = engineDataCache.begin(), + end = engineDataCache.end(); + while (it != end) { + if (it.key().screen == 0) { + ++it; + continue; + } + + if(it.value()->ref != 0) { + for(int i = 0; i < QUnicodeTables::ScriptCount; ++i) { + if(it.value()->engines[i]) { + it.value()->engines[i]->ref.deref(); + it.value()->engines[i] = 0; + } + } + ++it; + } else { + + EngineDataCache::Iterator rem = it++; + + decreaseCost(sizeof(QFontEngineData)); + + FC_DEBUG(" %p", rem.value()); + + delete rem.value(); + engineDataCache.erase(rem); + } + } + } + + EngineCache::Iterator it = engineCache.begin(), + end = engineCache.end(); + while(it != end) { + if (it.value().data->ref != 0 || it.key().screen == 0) { + ++it; + continue; + } + + FC_DEBUG(" %p: timestamp %4u hits %2u ref %2d/%2d, type '%s'", + it.value().data, it.value().timestamp, it.value().hits, + int(it.value().data->ref), it.value().data->cache_count, + it.value().data->name()); + + if (--it.value().data->cache_count == 0) { + FC_DEBUG(" DELETE: last occurrence in cache"); + + decreaseCost(it.value().data->cache_cost); + delete it.value().data; + } + + engineCache.erase(it++); + } +} +#endif + +void QFontCache::timerEvent(QTimerEvent *) +{ + FC_DEBUG("QFontCache::timerEvent: performing cache maintenance (timestamp %u)", + current_timestamp); + + if (total_cost <= max_cost && max_cost <= min_cost) { + FC_DEBUG(" cache redused sufficiently, stopping timer"); + + killTimer(timer_id); + timer_id = -1; + fast = false; + + return; + } + + // go through the cache and count up everything in use + uint in_use_cost = 0; + + { + FC_DEBUG(" SWEEP engine data:"); + + // make sure the cost of each engine data is at least 1kb + const uint engine_data_cost = + sizeof(QFontEngineData) > 1024 ? sizeof(QFontEngineData) : 1024; + + EngineDataCache::ConstIterator it = engineDataCache.constBegin(), + end = engineDataCache.constEnd(); + for (; it != end; ++it) { +#ifdef QFONTCACHE_DEBUG + FC_DEBUG(" %p: ref %2d", it.value(), int(it.value()->ref)); + +# if defined(Q_WS_X11) || defined(Q_WS_WIN) + // print out all engines + for (int i = 0; i < QUnicodeTables::ScriptCount; ++i) { + if (! it.value()->engines[i]) + continue; + FC_DEBUG(" contains %p", it.value()->engines[i]); + } +# endif // Q_WS_X11 || Q_WS_WIN +#endif // QFONTCACHE_DEBUG + + if (it.value()->ref != 0) + in_use_cost += engine_data_cost; + } + } + + { + FC_DEBUG(" SWEEP engine:"); + + EngineCache::ConstIterator it = engineCache.constBegin(), + end = engineCache.constEnd(); + for (; it != end; ++it) { + FC_DEBUG(" %p: timestamp %4u hits %2u ref %2d/%2d, cost %u bytes", + it.value().data, it.value().timestamp, it.value().hits, + int(it.value().data->ref), it.value().data->cache_count, + it.value().data->cache_cost); + + if (it.value().data->ref != 0) + in_use_cost += it.value().data->cache_cost / it.value().data->cache_count; + } + + // attempt to make up for rounding errors + in_use_cost += engineCache.size(); + } + + in_use_cost = (in_use_cost + 512) / 1024; // cost is stored in kb + + /* + calculate the new maximum cost for the cache + + NOTE: in_use_cost is *not* correct due to rounding errors in the + above algorithm. instead of worrying about getting the + calculation correct, we are more interested in speed, and use + in_use_cost as a floor for new_max_cost + */ + uint new_max_cost = qMax(qMax(max_cost / 2, in_use_cost), min_cost); + + FC_DEBUG(" after sweep, in use %u kb, total %u kb, max %u kb, new max %u kb", + in_use_cost, total_cost, max_cost, new_max_cost); + + if (new_max_cost == max_cost) { + if (fast) { + FC_DEBUG(" cannot shrink cache, slowing timer"); + + killTimer(timer_id); + timer_id = startTimer(slow_timeout); + fast = false; + } + + return; + } else if (! fast) { + FC_DEBUG(" dropping into passing gear"); + + killTimer(timer_id); + timer_id = startTimer(fast_timeout); + fast = true; + } + + max_cost = new_max_cost; + + { + FC_DEBUG(" CLEAN engine data:"); + + // clean out all unused engine data + EngineDataCache::Iterator it = engineDataCache.begin(), + end = engineDataCache.end(); + while (it != end) { + if (it.value()->ref != 0) { + ++it; + continue; + } + + EngineDataCache::Iterator rem = it++; + + decreaseCost(sizeof(QFontEngineData)); + + FC_DEBUG(" %p", rem.value()); + + delete rem.value(); + engineDataCache.erase(rem); + } + } + + // clean out the engine cache just enough to get below our new max cost + uint current_cost; + do { + current_cost = total_cost; + + EngineCache::Iterator it = engineCache.begin(), + end = engineCache.end(); + // determine the oldest and least popular of the unused engines + uint oldest = ~0u; + uint least_popular = ~0u; + + for (; it != end; ++it) { + if (it.value().data->ref != 0) + continue; + + if (it.value().timestamp < oldest && + it.value().hits <= least_popular) { + oldest = it.value().timestamp; + least_popular = it.value().hits; + } + } + + FC_DEBUG(" oldest %u least popular %u", oldest, least_popular); + + for (it = engineCache.begin(); it != end; ++it) { + if (it.value().data->ref == 0 && + it.value().timestamp == oldest && + it.value().hits == least_popular) + break; + } + + if (it != end) { + FC_DEBUG(" %p: timestamp %4u hits %2u ref %2d/%2d, type '%s'", + it.value().data, it.value().timestamp, it.value().hits, + int(it.value().data->ref), it.value().data->cache_count, + it.value().data->name()); + + if (--it.value().data->cache_count == 0) { + FC_DEBUG(" DELETE: last occurrence in cache"); + + decreaseCost(it.value().data->cache_cost); + delete it.value().data; + } else { + /* + this particular font engine is in the cache multiple + times... set current_cost to zero, so that we can + keep looping to get rid of all occurrences + */ + current_cost = 0; + } + + engineCache.erase(it); + } + } while (current_cost != total_cost && total_cost > max_cost); +} + + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug stream, const QFont &font) +{ + return stream << "QFont(" << font.toString() << ')'; +} +#endif + +QT_END_NAMESPACE diff --git a/src/gui/text/qfont.h b/src/gui/text/qfont.h new file mode 100644 index 0000000000..8dbc746813 --- /dev/null +++ b/src/gui/text/qfont.h @@ -0,0 +1,378 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONT_H +#define QFONT_H + +#include +#include +#include + +#if defined(Q_WS_X11) || defined(Q_WS_QWS) +typedef struct FT_FaceRec_* FT_Face; +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QFontPrivate; /* don't touch */ +class QStringList; +class QVariant; +class Q3TextFormatCollection; + +class Q_GUI_EXPORT QFont +{ + Q_GADGET + Q_ENUMS(StyleStrategy) +public: + enum StyleHint { + Helvetica, SansSerif = Helvetica, + Times, Serif = Times, + Courier, TypeWriter = Courier, + OldEnglish, Decorative = OldEnglish, + System, + AnyStyle, + Cursive, + Monospace, + Fantasy + }; + + enum StyleStrategy { + PreferDefault = 0x0001, + PreferBitmap = 0x0002, + PreferDevice = 0x0004, + PreferOutline = 0x0008, + ForceOutline = 0x0010, + PreferMatch = 0x0020, + PreferQuality = 0x0040, + PreferAntialias = 0x0080, + NoAntialias = 0x0100, + OpenGLCompatible = 0x0200, + ForceIntegerMetrics = 0x0400, + NoFontMerging = 0x8000 + }; + + enum HintingPreference { + PreferDefaultHinting = 0, + PreferNoHinting = 1, + PreferVerticalHinting = 2, + PreferFullHinting = 3 + }; + + enum Weight { + Light = 25, + Normal = 50, + DemiBold = 63, + Bold = 75, + Black = 87 + }; + + enum Style { + StyleNormal, + StyleItalic, + StyleOblique + }; + + enum Stretch { + UltraCondensed = 50, + ExtraCondensed = 62, + Condensed = 75, + SemiCondensed = 87, + Unstretched = 100, + SemiExpanded = 112, + Expanded = 125, + ExtraExpanded = 150, + UltraExpanded = 200 + }; + + enum Capitalization { + MixedCase, + AllUppercase, + AllLowercase, + SmallCaps, + Capitalize + }; + + enum SpacingType { + PercentageSpacing, + AbsoluteSpacing + }; + + enum ResolveProperties { + FamilyResolved = 0x0001, + SizeResolved = 0x0002, + StyleHintResolved = 0x0004, + StyleStrategyResolved = 0x0008, + WeightResolved = 0x0010, + StyleResolved = 0x0020, + UnderlineResolved = 0x0040, + OverlineResolved = 0x0080, + StrikeOutResolved = 0x0100, + FixedPitchResolved = 0x0200, + StretchResolved = 0x0400, + KerningResolved = 0x0800, + CapitalizationResolved = 0x1000, + LetterSpacingResolved = 0x2000, + WordSpacingResolved = 0x4000, + HintingPreferenceResolved = 0x8000, + AllPropertiesResolved = 0xffff + }; + + QFont(); + QFont(const QString &family, int pointSize = -1, int weight = -1, bool italic = false); + QFont(const QFont &, QPaintDevice *pd); + QFont(const QFont &); + ~QFont(); + + QString family() const; + void setFamily(const QString &); + + int pointSize() const; + void setPointSize(int); + qreal pointSizeF() const; + void setPointSizeF(qreal); + + int pixelSize() const; + void setPixelSize(int); + + int weight() const; + void setWeight(int); + + inline bool bold() const; + inline void setBold(bool); + + void setStyle(Style style); + Style style() const; + + inline bool italic() const; + inline void setItalic(bool b); + + bool underline() const; + void setUnderline(bool); + + bool overline() const; + void setOverline(bool); + + bool strikeOut() const; + void setStrikeOut(bool); + + bool fixedPitch() const; + void setFixedPitch(bool); + + bool kerning() const; + void setKerning(bool); + + StyleHint styleHint() const; + StyleStrategy styleStrategy() const; + void setStyleHint(StyleHint, StyleStrategy = PreferDefault); + void setStyleStrategy(StyleStrategy s); + + int stretch() const; + void setStretch(int); + + qreal letterSpacing() const; + SpacingType letterSpacingType() const; + void setLetterSpacing(SpacingType type, qreal spacing); + + qreal wordSpacing() const; + void setWordSpacing(qreal spacing); + + void setCapitalization(Capitalization); + Capitalization capitalization() const; + + void setHintingPreference(HintingPreference hintingPreference); + HintingPreference hintingPreference() const; + + // is raw mode still needed? + bool rawMode() const; + void setRawMode(bool); + + // dupicated from QFontInfo + bool exactMatch() const; + + QFont &operator=(const QFont &); + bool operator==(const QFont &) const; + bool operator!=(const QFont &) const; + bool operator<(const QFont &) const; + operator QVariant() const; + bool isCopyOf(const QFont &) const; +#ifdef Q_COMPILER_RVALUE_REFS + inline QFont &operator=(QFont &&other) + { qSwap(d, other.d); return *this; } +#endif + +#ifdef Q_WS_WIN + HFONT handle() const; +#else // !Q_WS_WIN + Qt::HANDLE handle() const; +#endif // Q_WS_WIN +#ifdef Q_WS_MAC + quint32 macFontID() const; +#endif +#if defined(Q_WS_X11) || defined(Q_WS_QWS) + FT_Face freetypeFace() const; +#endif + + // needed for X11 + void setRawName(const QString &); + QString rawName() const; + + QString key() const; + + QString toString() const; + bool fromString(const QString &); + + static QString substitute(const QString &); + static QStringList substitutes(const QString &); + static QStringList substitutions(); + static void insertSubstitution(const QString&, const QString &); + static void insertSubstitutions(const QString&, const QStringList &); + static void removeSubstitution(const QString &); + static void initialize(); + static void cleanup(); +#ifndef Q_WS_QWS + static void cacheStatistics(); +#endif + + QString defaultFamily() const; + QString lastResortFamily() const; + QString lastResortFont() const; + + QFont resolve(const QFont &) const; + inline uint resolve() const { return resolve_mask; } + inline void resolve(uint mask) { resolve_mask = mask; } + +#ifdef QT3_SUPPORT + static QT3_SUPPORT QFont defaultFont(); + static QT3_SUPPORT void setDefaultFont(const QFont &); + QT3_SUPPORT void setPixelSizeFloat(qreal); + QT3_SUPPORT qreal pointSizeFloat() const { return pointSizeF(); } + QT3_SUPPORT void setPointSizeFloat(qreal size) { setPointSizeF(size); } +#endif + +private: + QFont(QFontPrivate *); + + void detach(); + +#if defined(Q_WS_MAC) + void macSetFont(QPaintDevice *); +#elif defined(Q_WS_X11) + void x11SetScreen(int screen = -1); + int x11Screen() const; +#endif + + friend class QFontPrivate; + friend class QFontDialogPrivate; + friend class QFontMetrics; + friend class QFontMetricsF; + friend class QFontInfo; + friend class QPainter; + friend class QPainterPrivate; + friend class QPSPrintEngineFont; + friend class QApplication; + friend class QWidget; + friend class QWidgetPrivate; + friend class Q3TextFormatCollection; + friend class QTextLayout; + friend class QTextEngine; + friend class QStackTextEngine; + friend class QTextLine; + friend struct QScriptLine; + friend class QGLContext; + friend class QWin32PaintEngine; + friend class QAlphaPaintEngine; + friend class QPainterPath; + friend class QTextItemInt; + friend class QPicturePaintEngine; + friend class QPainterReplayer; + friend class QPaintBufferEngine; + friend class QCommandLinkButtonPrivate; + friend class QFontEngine; + +#ifndef QT_NO_DATASTREAM + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QFont &); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QFont &); +#endif + + QExplicitlySharedDataPointer d; + uint resolve_mask; +}; + + +inline bool QFont::bold() const +{ return weight() > Normal; } + + +inline void QFont::setBold(bool enable) +{ setWeight(enable ? Bold : Normal); } + +inline bool QFont::italic() const +{ + return (style() != StyleNormal); +} + +inline void QFont::setItalic(bool b) { + setStyle(b ? StyleItalic : StyleNormal); +} + + +/***************************************************************************** + QFont stream functions + *****************************************************************************/ + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QFont &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QFont &); +#endif + +#ifndef QT_NO_DEBUG_STREAM +Q_GUI_EXPORT QDebug operator<<(QDebug, const QFont &); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFONT_H diff --git a/src/gui/text/qfont_mac.cpp b/src/gui/text/qfont_mac.cpp new file mode 100644 index 0000000000..daf68c03ea --- /dev/null +++ b/src/gui/text/qfont_mac.cpp @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfont.h" +#include "qfont_p.h" +#include "qfontengine_p.h" +#include "qfontengine_mac_p.h" +#include "qfontinfo.h" +#include "qfontmetrics.h" +#include "qpaintdevice.h" +#include "qstring.h" +#include +#include +#include +#include +#include "qfontdatabase.h" +#include +#include "qtextengine_p.h" +#include + +QT_BEGIN_NAMESPACE + +extern float qt_mac_defaultDpi_x(); //qpaintdevice_mac.cpp + +int qt_mac_pixelsize(const QFontDef &def, int dpi) +{ + float ret; + if(def.pixelSize == -1) + ret = def.pointSize * dpi / qt_mac_defaultDpi_x(); + else + ret = def.pixelSize; + return qRound(ret); +} +int qt_mac_pointsize(const QFontDef &def, int dpi) +{ + float ret; + if(def.pointSize < 0) + ret = def.pixelSize * qt_mac_defaultDpi_x() / float(dpi); + else + ret = def.pointSize; + return qRound(ret); +} + +QString QFont::rawName() const +{ + return family(); +} + +void QFont::setRawName(const QString &name) +{ + setFamily(name); +} + +void QFont::cleanup() +{ + QFontCache::cleanup(); +} + +/*! + Returns an ATSUFontID +*/ +quint32 QFont::macFontID() const // ### need 64-bit version +{ +#ifdef QT_MAC_USE_COCOA + return 0; +#elif 1 + QFontEngine *fe = d->engineForScript(QUnicodeTables::Common); + if (fe && fe->type() == QFontEngine::Multi) + return static_cast(fe)->macFontID(); +#else + Str255 name; + if(FMGetFontFamilyName((FMFontFamily)((UInt32)handle()), name) == noErr) { + short fnum; + GetFNum(name, &fnum); + return fnum; + } +#endif + return 0; +} + +// Returns an ATSUFonFamilyRef +Qt::HANDLE QFont::handle() const +{ +#if 0 + QFontEngine *fe = d->engineForScript(QUnicodeTables::Common); + if (fe && fe->type() == QFontEngine::Mac) + return (Qt::HANDLE)static_cast(fe)->fontFamilyRef(); +#endif + return 0; +} + +void QFont::initialize() +{ } + +QString QFont::defaultFamily() const +{ + switch(d->request.styleHint) { + case QFont::Times: + return QString::fromLatin1("Times New Roman"); + case QFont::Courier: + return QString::fromLatin1("Courier New"); + case QFont::Monospace: + return QString::fromLatin1("Courier"); + case QFont::Decorative: + return QString::fromLatin1("Bookman Old Style"); + case QFont::Cursive: + return QString::fromLatin1("Apple Chancery"); + case QFont::Fantasy: + return QString::fromLatin1("Papyrus"); + case QFont::Helvetica: + case QFont::System: + default: + return QString::fromLatin1("Helvetica"); + } +} + +QString QFont::lastResortFamily() const +{ + return QString::fromLatin1("Helvetica"); +} + +QString QFont::lastResortFont() const +{ + return QString::fromLatin1("Geneva"); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h new file mode 100644 index 0000000000..43dca81e55 --- /dev/null +++ b/src/gui/text/qfont_p.h @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONT_P_H +#define QFONT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of internal files. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qfont.h" +#include "QtCore/qmap.h" +#include "QtCore/qobject.h" +#include +#include +#include "private/qfixed_p.h" + +QT_BEGIN_NAMESPACE + +// forwards +class QFontCache; +class QFontEngine; + +struct QFontDef +{ + inline QFontDef() + : pointSize(-1.0), pixelSize(-1), + styleStrategy(QFont::PreferDefault), styleHint(QFont::AnyStyle), + weight(50), fixedPitch(false), style(QFont::StyleNormal), stretch(100), + ignorePitch(true), hintingPreference(QFont::PreferDefaultHinting) +#ifdef Q_WS_MAC + ,fixedPitchComputed(false) +#endif + { + } + + QString family; + +#ifdef Q_WS_X11 + QString addStyle; +#endif // Q_WS_X11 + + qreal pointSize; + qreal pixelSize; + + uint styleStrategy : 16; + uint styleHint : 8; + + uint weight : 7; // 0-99 + uint fixedPitch : 1; + uint style : 2; + uint stretch : 12; // 0-400 + + uint ignorePitch : 1; + uint hintingPreference : 2; + uint fixedPitchComputed : 1; // for Mac OS X only + int reserved : 14; // for future extensions + + bool exactMatch(const QFontDef &other) const; + bool operator==(const QFontDef &other) const + { + return pixelSize == other.pixelSize + && weight == other.weight + && style == other.style + && stretch == other.stretch + && styleHint == other.styleHint + && styleStrategy == other.styleStrategy + && ignorePitch == other.ignorePitch && fixedPitch == other.fixedPitch + && family == other.family + && hintingPreference == other.hintingPreference +#ifdef Q_WS_X11 + && addStyle == other.addStyle +#endif + ; + } + inline bool operator<(const QFontDef &other) const + { + if (pixelSize != other.pixelSize) return pixelSize < other.pixelSize; + if (weight != other.weight) return weight < other.weight; + if (style != other.style) return style < other.style; + if (stretch != other.stretch) return stretch < other.stretch; + if (styleHint != other.styleHint) return styleHint < other.styleHint; + if (styleStrategy != other.styleStrategy) return styleStrategy < other.styleStrategy; + if (family != other.family) return family < other.family; + if (hintingPreference != other.hintingPreference) return hintingPreference < other.hintingPreference; + +#ifdef Q_WS_X11 + if (addStyle != other.addStyle) return addStyle < other.addStyle; +#endif // Q_WS_X11 + + if (ignorePitch != other.ignorePitch) return ignorePitch < other.ignorePitch; + if (fixedPitch != other.fixedPitch) return fixedPitch < other.fixedPitch; + return false; + } +}; + +class QFontEngineData +{ +public: + QFontEngineData(); + ~QFontEngineData(); + + QAtomicInt ref; + QFontCache *fontCache; + +#if !defined(Q_WS_MAC) + QFontEngine *engines[QUnicodeTables::ScriptCount]; +#else + QFontEngine *engine; +#endif +}; + + +class Q_GUI_EXPORT QFontPrivate +{ +public: +#ifdef Q_WS_X11 + static int defaultEncodingID; +#endif // Q_WS_X11 + + QFontPrivate(); + QFontPrivate(const QFontPrivate &other); + ~QFontPrivate(); + + QFontEngine *engineForScript(int script) const; + void alterCharForCapitalization(QChar &c) const; + + QAtomicInt ref; + QFontDef request; + mutable QFontEngineData *engineData; + int dpi; + int screen; + +#ifdef Q_WS_WIN + HDC hdc; +#endif + + uint rawMode : 1; + uint underline : 1; + uint overline : 1; + uint strikeOut : 1; + uint kerning : 1; + uint capital : 3; + bool letterSpacingIsAbsolute : 1; + + QFixed letterSpacing; + QFixed wordSpacing; + + mutable QFontPrivate *scFont; + QFont smallCapsFont() const { return QFont(smallCapsFontPrivate()); } + QFontPrivate *smallCapsFontPrivate() const; + + static QFontPrivate *get(const QFont &font) + { + return font.d.data(); + } + + void resolve(uint mask, const QFontPrivate *other); +private: + QFontPrivate &operator=(const QFontPrivate &) { return *this; } +}; + + +class QFontCache : public QObject +{ + Q_OBJECT +public: + // note: these static functions work on a per-thread basis + static QFontCache *instance(); + static void cleanup(); + + QFontCache(); + ~QFontCache(); + + void clear(); +#if defined(Q_WS_QWS) && !defined(QT_NO_QWS_QPF2) + void removeEngineForFont(const QByteArray &fontName); +#endif + // universal key structure. QFontEngineDatas and QFontEngines are cached using + // the same keys + struct Key { + Key() : script(0), screen(0) { } + Key(const QFontDef &d, int c, int s = 0) + : def(d), script(c), screen(s) { } + + QFontDef def; + int script; + int screen; + + inline bool operator<(const Key &other) const + { + if (script != other.script) return script < other.script; + if (screen != other.screen) return screen < other.screen; + return def < other.def; + } + inline bool operator==(const Key &other) const + { return def == other.def && script == other.script && screen == other.screen; } + }; + + // QFontEngineData cache + typedef QMap EngineDataCache; + EngineDataCache engineDataCache; + + QFontEngineData *findEngineData(const Key &key) const; + void insertEngineData(const Key &key, QFontEngineData *engineData); + + // QFontEngine cache + struct Engine { + Engine() : data(0), timestamp(0), hits(0) { } + Engine(QFontEngine *d) : data(d), timestamp(0), hits(0) { } + + QFontEngine *data; + uint timestamp; + uint hits; + }; + + typedef QMap EngineCache; + EngineCache engineCache; + + QFontEngine *findEngine(const Key &key); + void insertEngine(const Key &key, QFontEngine *engine); + +#if defined(Q_WS_WIN) || defined(Q_WS_QWS) + void cleanupPrinterFonts(); +#endif + + private: + void increaseCost(uint cost); + void decreaseCost(uint cost); + void timerEvent(QTimerEvent *event); + + static const uint min_cost; + uint total_cost, max_cost; + uint current_timestamp; + bool fast; + int timer_id; +}; + +Q_GUI_EXPORT int qt_defaultDpiX(); +Q_GUI_EXPORT int qt_defaultDpiY(); +Q_GUI_EXPORT int qt_defaultDpi(); + +QT_END_NAMESPACE + +#endif // QFONT_P_H diff --git a/src/gui/text/qfont_qpa.cpp b/src/gui/text/qfont_qpa.cpp new file mode 100644 index 0000000000..ff12da8d97 --- /dev/null +++ b/src/gui/text/qfont_qpa.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +QT_BEGIN_NAMESPACE + +void QFont::initialize() +{ + QApplicationPrivate::platformIntegration()->fontDatabase()->populateFontDatabase(); +} + +void QFont::cleanup() +{ + QFontCache::cleanup(); +} + + +/***************************************************************************** + QFont member functions + *****************************************************************************/ + +Qt::HANDLE QFont::handle() const +{ + return 0; +} + +QString QFont::rawName() const +{ + return QLatin1String("unknown"); +} + +void QFont::setRawName(const QString &) +{ +} + +QString QFont::defaultFamily() const +{ + QString familyName; + switch(d->request.styleHint) { + case QFont::Times: + familyName = QString::fromLatin1("times"); + case QFont::Courier: + case QFont::Monospace: + familyName = QString::fromLatin1("monospace"); + case QFont::Decorative: + familyName = QString::fromLatin1("old english"); + case QFont::Helvetica: + case QFont::System: + default: + familyName = QString::fromLatin1("helvetica"); + } + + QStringList list = QApplicationPrivate::platformIntegration()->fontDatabase()->fallbacksForFamily(familyName,QFont::StyleNormal,QFont::StyleHint(d->request.styleHint),QUnicodeTables::Common); + if (list.size()) { + familyName = list.at(0); + } + return familyName; +} + +QString QFont::lastResortFamily() const +{ + return QString::fromLatin1("helvetica"); +} + +QString QFont::lastResortFont() const +{ + qFatal("QFont::lastResortFont: Cannot find any reasonable font"); + // Shut compiler up + return QString(); +} + + +QT_END_NAMESPACE + diff --git a/src/gui/text/qfont_qws.cpp b/src/gui/text/qfont_qws.cpp new file mode 100644 index 0000000000..ea2a944432 --- /dev/null +++ b/src/gui/text/qfont_qws.cpp @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwidget.h" +#include "qpainter.h" +#include "qfont_p.h" +#include +#include "qfontdatabase.h" +#include "qtextcodec.h" +#include "qapplication.h" +#include "qfile.h" +#include "qtextstream.h" +#include "qmap.h" +//#include "qmemorymanager_qws.h" +#include "qtextengine_p.h" +#include "qfontengine_p.h" +#if !defined(QT_NO_FREETYPE) +#include "qfontengine_ft_p.h" +#endif + +QT_BEGIN_NAMESPACE + +void QFont::initialize() +{ } + +void QFont::cleanup() +{ + QFontCache::cleanup(); +} + + +/***************************************************************************** + QFont member functions + *****************************************************************************/ + +Qt::HANDLE QFont::handle() const +{ +#ifndef QT_NO_FREETYPE + return freetypeFace(); +#endif + return 0; +} + +FT_Face QFont::freetypeFace() const +{ +#ifndef QT_NO_FREETYPE + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + if (engine->type() == QFontEngine::Multi) + engine = static_cast(engine)->engine(0); + if (engine->type() == QFontEngine::Freetype) { + const QFontEngineFT *ft = static_cast(engine); + return ft->non_locked_face(); + } +#endif + return 0; +} + +QString QFont::rawName() const +{ + return QLatin1String("unknown"); +} + +void QFont::setRawName(const QString &) +{ +} + +QString QFont::defaultFamily() const +{ + switch(d->request.styleHint) { + case QFont::Times: + return QString::fromLatin1("times"); + case QFont::Courier: + case QFont::Monospace: + return QString::fromLatin1("courier"); + case QFont::Decorative: + return QString::fromLatin1("old english"); + case QFont::Helvetica: + case QFont::System: + default: + return QString::fromLatin1("helvetica"); + } +} + +QString QFont::lastResortFamily() const +{ + return QString::fromLatin1("helvetica"); +} + +QString QFont::lastResortFont() const +{ + qFatal("QFont::lastResortFont: Cannot find any reasonable font"); + // Shut compiler up + return QString(); +} + + +QT_END_NAMESPACE diff --git a/src/gui/text/qfont_s60.cpp b/src/gui/text/qfont_s60.cpp new file mode 100644 index 0000000000..114191d765 --- /dev/null +++ b/src/gui/text/qfont_s60.cpp @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfont.h" +#include "qfont_p.h" +#include +#include +#include "qmutex.h" + +QT_BEGIN_NAMESPACE + +#ifdef QT_NO_FREETYPE +Q_GLOBAL_STATIC(QMutex, lastResortFamilyMutex); +#endif // QT_NO_FREETYPE + +extern QStringList qt_symbian_fontFamiliesOnFontServer(); // qfontdatabase_s60.cpp +Q_GLOBAL_STATIC_WITH_INITIALIZER(QStringList, fontFamiliesOnFontServer, { + // We are only interested in the initial font families. No Application fonts. + // Therefore, we are allowed to cache the list. + x->append(qt_symbian_fontFamiliesOnFontServer()); +}); + +QString QFont::lastResortFont() const +{ + // Symbian's font Api does not distinguish between font and family. + // Therefore we try to get a "Family" first, then fall back to "Sans". + static QString font = lastResortFamily(); + if (font.isEmpty()) + font = QLatin1String("Sans"); + return font; +} + +QString QFont::lastResortFamily() const +{ +#ifdef QT_NO_FREETYPE + QMutexLocker locker(lastResortFamilyMutex()); + static QString family; + if (family.isEmpty()) { + + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + + CFont *font; + const TInt err = S60->screenDevice()->GetNearestFontInTwips(font, TFontSpec()); + Q_ASSERT(err == KErrNone); + const TFontSpec spec = font->FontSpecInTwips(); + family = QString((const QChar *)spec.iTypeface.iName.Ptr(), spec.iTypeface.iName.Length()); + S60->screenDevice()->ReleaseFont(font); + + lock.relock(); + } + return family; +#else // QT_NO_FREETYPE + // For the FreeType case we just hard code the face name, since otherwise on + // East Asian systems we may get a name for a stroke based (non-ttf) font. + + // TODO: Get the type face name in a proper way + + const bool isJapaneseOrChineseSystem = + User::Language() == ELangJapanese || User::Language() == ELangPrcChinese; + + static QString family; + if (family.isEmpty()) { + QStringList families = qt_symbian_fontFamiliesOnFontServer(); + const char* const preferredFamilies[] = {"Nokia Sans S60", "Series 60 Sans"}; + for (int i = 0; i < sizeof preferredFamilies / sizeof preferredFamilies[0]; ++i) { + const QString preferredFamily = QLatin1String(preferredFamilies[i]); + if (families.contains(preferredFamily)) { + family = preferredFamily; + break; + } + } + } + + return QLatin1String(isJapaneseOrChineseSystem?"Heisei Kaku Gothic S60":family.toLatin1()); +#endif // QT_NO_FREETYPE +} + +QString QFont::defaultFamily() const +{ +#ifdef QT_NO_FREETYPE + switch(d->request.styleHint) { + case QFont::SansSerif: { + static const char* const preferredSansSerif[] = {"Nokia Sans S60", "Series 60 Sans"}; + for (int i = 0; i < sizeof preferredSansSerif / sizeof preferredSansSerif[0]; ++i) { + const QString sansSerif = QLatin1String(preferredSansSerif[i]); + if (fontFamiliesOnFontServer()->contains(sansSerif)) + return sansSerif; + } + } + // No break. Intentional fall through. + default: + return lastResortFamily(); + } +#endif // QT_NO_FREETYPE + return lastResortFamily(); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfont_win.cpp b/src/gui/text/qfont_win.cpp new file mode 100644 index 0000000000..3ef761bfa5 --- /dev/null +++ b/src/gui/text/qfont_win.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfont.h" +#include "qfont_p.h" +#include "qfontengine_p.h" +#include "qtextengine_p.h" +#include "qfontmetrics.h" +#include "qfontinfo.h" + +#include "qwidget.h" +#include "qpainter.h" +#include +#include "qt_windows.h" +#include +#include "qapplication.h" +#include +#include + +QT_BEGIN_NAMESPACE + +extern HDC shared_dc(); // common dc for all fonts +extern QFont::Weight weightFromInteger(int weight); // qfontdatabase.cpp + +// ### maybe move to qapplication_win +QFont qt_LOGFONTtoQFont(LOGFONT& lf, bool /*scale*/) +{ + QString family = QString::fromWCharArray(lf.lfFaceName); + QFont qf(family); + qf.setItalic(lf.lfItalic); + if (lf.lfWeight != FW_DONTCARE) + qf.setWeight(weightFromInteger(lf.lfWeight)); + int lfh = qAbs(lf.lfHeight); + qf.setPointSizeF(lfh * 72.0 / GetDeviceCaps(shared_dc(),LOGPIXELSY)); + qf.setUnderline(false); + qf.setOverline(false); + qf.setStrikeOut(false); + return qf; +} + + +static inline float pixelSize(const QFontDef &request, int dpi) +{ + float pSize; + if (request.pointSize != -1) + pSize = request.pointSize * dpi/ 72.; + else + pSize = request.pixelSize; + return pSize; +} + +static inline float pointSize(const QFontDef &fd, int dpi) +{ + float pSize; + if (fd.pointSize < 0) + pSize = fd.pixelSize * 72. / ((float)dpi); + else + pSize = fd.pointSize; + return pSize; +} + +/***************************************************************************** + QFont member functions + *****************************************************************************/ + +void QFont::initialize() +{ +} + +void QFont::cleanup() +{ + QFontCache::cleanup(); +} + +HFONT QFont::handle() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Multi) + engine = static_cast(engine)->engine(0); + if (engine->type() == QFontEngine::Win) + return static_cast(engine)->hfont; + return 0; +} + +QString QFont::rawName() const +{ + return family(); +} + +void QFont::setRawName(const QString &name) +{ + setFamily(name); +} + +QString QFont::defaultFamily() const +{ + switch(d->request.styleHint) { + case QFont::Times: + return QString::fromLatin1("Times New Roman"); + case QFont::Courier: + case QFont::Monospace: + return QString::fromLatin1("Courier New"); + case QFont::Decorative: + return QString::fromLatin1("Bookman Old Style"); + case QFont::Cursive: + return QString::fromLatin1("Comic Sans MS"); + case QFont::Fantasy: + return QString::fromLatin1("Impact"); + case QFont::Helvetica: + return QString::fromLatin1("Arial"); + case QFont::System: + default: + return QString::fromLatin1("MS Sans Serif"); + } +} + +QString QFont::lastResortFamily() const +{ + return QString::fromLatin1("helvetica"); +} + +QString QFont::lastResortFont() const +{ + return QString::fromLatin1("arial"); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfont_x11.cpp b/src/gui/text/qfont_x11.cpp new file mode 100644 index 0000000000..c72a5fade5 --- /dev/null +++ b/src/gui/text/qfont_x11.cpp @@ -0,0 +1,368 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#define QT_FATAL_ASSERT + +#include "qplatformdefs.h" + +#include "qfont.h" +#include "qapplication.h" +#include "qfontinfo.h" +#include "qfontdatabase.h" +#include "qfontmetrics.h" +#include "qpaintdevice.h" +#include "qtextcodec.h" +#include "qiodevice.h" +#include "qhash.h" + +#include +#include "qfont_p.h" +#include "qfontengine_p.h" +#include "qfontengine_x11_p.h" +#include "qtextengine_p.h" + +#include +#include "qx11info_x11.h" + +#include +#include +#include + +#define QFONTLOADER_DEBUG +#define QFONTLOADER_DEBUG_VERBOSE + +QT_BEGIN_NAMESPACE + +double qt_pixelSize(double pointSize, int dpi) +{ + if (pointSize < 0) + return -1.; + if (dpi == 75) // the stupid 75 dpi setting on X11 + dpi = 72; + return (pointSize * dpi) /72.; +} + +double qt_pointSize(double pixelSize, int dpi) +{ + if (pixelSize < 0) + return -1.; + if (dpi == 75) // the stupid 75 dpi setting on X11 + dpi = 72; + return pixelSize * 72. / ((double) dpi); +} + +/* + Removes wildcards from an XLFD. + + Returns \a xlfd with all wildcards removed if a match for \a xlfd is + found, otherwise it returns \a xlfd. +*/ +static QByteArray qt_fixXLFD(const QByteArray &xlfd) +{ + QByteArray ret = xlfd; + int count = 0; + char **fontNames = + XListFonts(QX11Info::display(), xlfd, 32768, &count); + if (count > 0) + ret = fontNames[0]; + XFreeFontNames(fontNames); + return ret ; +} + +typedef QHash FallBackHash; +Q_GLOBAL_STATIC(FallBackHash, fallBackHash) + +// Returns the user-configured fallback family for the specified script. +QString qt_fallback_font_family(int script) +{ + FallBackHash *hash = fallBackHash(); + return hash->value(script); +} + +// Sets the fallback family for the specified script. +Q_GUI_EXPORT void qt_x11_set_fallback_font_family(int script, const QString &family) +{ + FallBackHash *hash = fallBackHash(); + if (!family.isEmpty()) + hash->insert(script, family); + else + hash->remove(script); +} + +int QFontPrivate::defaultEncodingID = -1; + +void QFont::initialize() +{ + extern int qt_encoding_id_for_mib(int mib); // from qfontdatabase_x11.cpp + QTextCodec *codec = QTextCodec::codecForLocale(); + // determine the default encoding id using the locale, otherwise + // fallback to latin1 (mib == 4) + int mib = codec ? codec->mibEnum() : 4; + + // for asian locales, use the mib for the font codec instead of the locale codec + switch (mib) { + case 38: // eucKR + mib = 36; + break; + + case 2025: // GB2312 + mib = 57; + break; + + case 113: // GBK + mib = -113; + break; + + case 114: // GB18030 + mib = -114; + break; + + case 2026: // Big5 + mib = -2026; + break; + + case 2101: // Big5-HKSCS + mib = -2101; + break; + + case 16: // JIS7 + mib = 15; + break; + + case 17: // SJIS + case 18: // eucJP + mib = 63; + break; + } + + // get the default encoding id for the locale encoding... + QFontPrivate::defaultEncodingID = qt_encoding_id_for_mib(mib); +} + +void QFont::cleanup() +{ + QFontCache::cleanup(); +} + +/*! + \internal + X11 Only: Returns the screen with which this font is associated. +*/ +int QFont::x11Screen() const +{ + return d->screen; +} + +/*! \internal + X11 Only: Associate the font with the specified \a screen. +*/ +void QFont::x11SetScreen(int screen) +{ + if (screen < 0) // assume default + screen = QX11Info::appScreen(); + + if (screen == d->screen) + return; // nothing to do + + detach(); + d->screen = screen; +} + +Qt::HANDLE QFont::handle() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Multi) + engine = static_cast(engine)->engine(0); + if (engine->type() == QFontEngine::XLFD) + return static_cast(engine)->fontStruct()->fid; + return 0; +} + + +FT_Face QFont::freetypeFace() const +{ +#ifndef QT_NO_FREETYPE + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + if (engine->type() == QFontEngine::Multi) + engine = static_cast(engine)->engine(0); +#ifndef QT_NO_FONTCONFIG + if (engine->type() == QFontEngine::Freetype) { + const QFontEngineFT *ft = static_cast(engine); + return ft->non_locked_face(); + } else +#endif + if (engine->type() == QFontEngine::XLFD) { + const QFontEngineXLFD *xlfd = static_cast(engine); + return xlfd->non_locked_face(); + } +#endif + return 0; +} + +QString QFont::rawName() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Multi) + engine = static_cast(engine)->engine(0); + if (engine->type() == QFontEngine::XLFD) + return QString::fromLatin1(engine->name()); + return QString(); +} +struct QtFontDesc; + +void QFont::setRawName(const QString &name) +{ + detach(); + + // from qfontdatabase_x11.cpp + extern bool qt_fillFontDef(const QByteArray &xlfd, QFontDef *fd, int dpi, QtFontDesc *desc); + + if (!qt_fillFontDef(qt_fixXLFD(name.toLatin1()), &d->request, d->dpi, 0)) { + qWarning("QFont::setRawName: Invalid XLFD: \"%s\"", name.toLatin1().constData()); + + setFamily(name); + setRawMode(true); + } else { + resolve_mask = QFont::AllPropertiesResolved; + } +} + +QString QFont::lastResortFamily() const +{ + return QString::fromLatin1("Helvetica"); +} + +QString QFont::defaultFamily() const +{ + switch (d->request.styleHint) { + case QFont::Times: + return QString::fromLatin1("Times"); + + case QFont::Courier: + return QString::fromLatin1("Courier"); + + case QFont::Monospace: + return QString::fromLatin1("Courier New"); + + case QFont::Cursive: + return QString::fromLatin1("Comic Sans MS"); + + case QFont::Fantasy: + return QString::fromLatin1("Impact"); + + case QFont::Decorative: + return QString::fromLatin1("Old English"); + + case QFont::Helvetica: + case QFont::System: + default: + return QString::fromLatin1("Helvetica"); + } +} + +/* + Returns a last resort raw font name for the font matching algorithm. + This is used if even the last resort family is not available. It + returns \e something, almost no matter what. The current + implementation tries a wide variety of common fonts, returning the + first one it finds. The implementation may change at any time. +*/ +static const char * const tryFonts[] = { + "-*-helvetica-medium-r-*-*-*-120-*-*-*-*-*-*", + "-*-courier-medium-r-*-*-*-120-*-*-*-*-*-*", + "-*-times-medium-r-*-*-*-120-*-*-*-*-*-*", + "-*-lucida-medium-r-*-*-*-120-*-*-*-*-*-*", + "-*-helvetica-*-*-*-*-*-120-*-*-*-*-*-*", + "-*-courier-*-*-*-*-*-120-*-*-*-*-*-*", + "-*-times-*-*-*-*-*-120-*-*-*-*-*-*", + "-*-lucida-*-*-*-*-*-120-*-*-*-*-*-*", + "-*-helvetica-*-*-*-*-*-*-*-*-*-*-*-*", + "-*-courier-*-*-*-*-*-*-*-*-*-*-*-*", + "-*-times-*-*-*-*-*-*-*-*-*-*-*-*", + "-*-lucida-*-*-*-*-*-*-*-*-*-*-*-*", + "-*-fixed-*-*-*-*-*-*-*-*-*-*-*-*", + "6x13", + "7x13", + "8x13", + "9x15", + "fixed", + 0 +}; + +// Returns true if the font exists, false otherwise +static bool fontExists(const QString &fontName) +{ + int count; + char **fontNames = XListFonts(QX11Info::display(), (char*)fontName.toLatin1().constData(), 32768, &count); + if (fontNames) XFreeFontNames(fontNames); + + return count != 0; +} + +QString QFont::lastResortFont() const +{ + static QString last; + + // already found + if (! last.isNull()) + return last; + + int i = 0; + const char* f; + + while ((f = tryFonts[i])) { + last = QString::fromLatin1(f); + + if (fontExists(last)) + return last; + + i++; + } + +#if defined(CHECK_NULL) + qFatal("QFontPrivate::lastResortFont: Cannot find any reasonable font"); +#endif + return last; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontdatabase.cpp b/src/gui/text/qfontdatabase.cpp new file mode 100644 index 0000000000..36b0ea9b68 --- /dev/null +++ b/src/gui/text/qfontdatabase.cpp @@ -0,0 +1,2715 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "qfontdatabase.h" +#include "qdebug.h" +#include "qalgorithms.h" +#include "qapplication.h" +#include "qvarlengtharray.h" // here or earlier - workaround for VC++6 +#include "qthread.h" +#include "qmutex.h" +#include "private/qunicodetables_p.h" +#include "qfontengine_p.h" + +#ifdef Q_WS_QPA +#include +#include +#include "qabstractfileengine.h" +#endif + +#ifdef Q_WS_X11 +#include +#endif +#include +#include + +#if (defined(Q_WS_QWS)|| defined(Q_OS_SYMBIAN)) && !defined(QT_NO_FREETYPE) +# include +# include FT_TRUETYPE_TABLES_H +#endif + +// #define QFONTDATABASE_DEBUG +#ifdef QFONTDATABASE_DEBUG +# define FD_DEBUG qDebug +#else +# define FD_DEBUG if (false) qDebug +#endif + +// #define FONT_MATCH_DEBUG +#ifdef FONT_MATCH_DEBUG +# define FM_DEBUG qDebug +#else +# define FM_DEBUG if (false) qDebug +#endif + +#if defined(Q_WS_WIN) && !defined(QT_NO_DIRECTWRITE) +# include +#endif + +QT_BEGIN_NAMESPACE + +#define SMOOTH_SCALABLE 0xffff + +bool qt_enable_test_font = false; + +Q_AUTOTEST_EXPORT void qt_setQtEnableTestFont(bool value) +{ + qt_enable_test_font = value; +} + +static int getFontWeight(const QString &weightString) +{ + QString s = weightString.toLower(); + + // Test in decreasing order of commonness + if (s == QLatin1String("medium") || + s == QLatin1String("normal") + || s.compare(QApplication::translate("QFontDatabase", "Normal"), Qt::CaseInsensitive) == 0) + return QFont::Normal; + if (s == QLatin1String("bold") + || s.compare(QApplication::translate("QFontDatabase", "Bold"), Qt::CaseInsensitive) == 0) + return QFont::Bold; + if (s == QLatin1String("demibold") || s == QLatin1String("demi bold") + || s.compare(QApplication::translate("QFontDatabase", "Demi Bold"), Qt::CaseInsensitive) == 0) + return QFont::DemiBold; + if (s == QLatin1String("black") + || s.compare(QApplication::translate("QFontDatabase", "Black"), Qt::CaseInsensitive) == 0) + return QFont::Black; + if (s == QLatin1String("light")) + return QFont::Light; + + if (s.contains(QLatin1String("bold")) + || s.contains(QApplication::translate("QFontDatabase", "Bold"), Qt::CaseInsensitive)) { + if (s.contains(QLatin1String("demi")) + || s.compare(QApplication::translate("QFontDatabase", "Demi"), Qt::CaseInsensitive) == 0) + return (int) QFont::DemiBold; + return (int) QFont::Bold; + } + + if (s.contains(QLatin1String("light")) + || s.compare(QApplication::translate("QFontDatabase", "Light"), Qt::CaseInsensitive) == 0) + return (int) QFont::Light; + + if (s.contains(QLatin1String("black")) + || s.compare(QApplication::translate("QFontDatabase", "Black"), Qt::CaseInsensitive) == 0) + return (int) QFont::Black; + + return (int) QFont::Normal; +} + +// convert 0 ~ 1000 integer to QFont::Weight +QFont::Weight weightFromInteger(int weight) +{ + if (weight < 400) + return QFont::Light; + else if (weight < 600) + return QFont::Normal; + else if (weight < 700) + return QFont::DemiBold; + else if (weight < 800) + return QFont::Bold; + else + return QFont::Black; +} + +struct QtFontEncoding +{ + signed int encoding : 16; + + uint xpoint : 16; + uint xres : 8; + uint yres : 8; + uint avgwidth : 16; + uchar pitch : 8; +}; + +struct QtFontSize +{ +#ifdef Q_WS_X11 + QtFontEncoding *encodings; + QtFontEncoding *encodingID(int id, uint xpoint = 0, uint xres = 0, + uint yres = 0, uint avgwidth = 0, bool add = false); + unsigned short count : 16; +#endif // Q_WS_X11 + +#if defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN) + QByteArray fileName; + int fileIndex; +#endif // defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) +#if defined(Q_WS_QPA) + void *handle; +#endif + + unsigned short pixelSize : 16; +}; + + +#ifdef Q_WS_X11 +QtFontEncoding *QtFontSize::encodingID(int id, uint xpoint, uint xres, + uint yres, uint avgwidth, bool add) +{ + // we don't match using the xpoint, xres and yres parameters, only the id + for (int i = 0; i < count; ++i) { + if (encodings[i].encoding == id) + return encodings + i; + } + + if (!add) return 0; + + if (!(count % 4)) { + QtFontEncoding *newEncodings = (QtFontEncoding *) + realloc(encodings, + (((count+4) >> 2) << 2) * sizeof(QtFontEncoding)); + Q_CHECK_PTR(newEncodings); + encodings = newEncodings; + } + encodings[count].encoding = id; + encodings[count].xpoint = xpoint; + encodings[count].xres = xres; + encodings[count].yres = yres; + encodings[count].avgwidth = avgwidth; + encodings[count].pitch = '*'; + return encodings + count++; +} +#endif // Q_WS_X11 + +struct QtFontStyle +{ + struct Key { + Key(const QString &styleString); + Key() : style(QFont::StyleNormal), + weight(QFont::Normal), stretch(0) { } + Key(const Key &o) : style(o.style), + weight(o.weight), stretch(o.stretch) { } + uint style : 2; + signed int weight : 8; + signed int stretch : 12; + + bool operator==(const Key & other) { + return (style == other.style && + weight == other.weight && + (stretch == 0 || other.stretch == 0 || stretch == other.stretch)); + } + bool operator!=(const Key &other) { + return !operator==(other); + } + bool operator <(const Key &o) { + int x = (style << 12) + (weight << 14) + stretch; + int y = (o.style << 12) + (o.weight << 14) + o.stretch; + return (x < y); + } + }; + + QtFontStyle(const Key &k) + : key(k), bitmapScalable(false), smoothScalable(false), + count(0), pixelSizes(0) + { +#if defined(Q_WS_X11) + weightName = setwidthName = 0; +#endif // Q_WS_X11 + } + + ~QtFontStyle() { +#ifdef Q_WS_X11 + delete [] weightName; + delete [] setwidthName; +#endif +#if defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) + while (count) { + // bitfield count-- in while condition does not work correctly in mwccsym2 + count--; +#ifdef Q_WS_X11 + free(pixelSizes[count].encodings); +#endif +#if defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN) + pixelSizes[count].fileName.~QByteArray(); +#endif +#if defined (Q_WS_QPA) + QPlatformIntegration *integration = QApplicationPrivate::platformIntegration(); + if (integration) { //on shut down there will be some that we don't release. + integration->fontDatabase()->releaseHandle(pixelSizes[count].handle); + } +#endif + } +#endif + free(pixelSizes); + } + + Key key; + bool bitmapScalable : 1; + bool smoothScalable : 1; + signed int count : 30; + QtFontSize *pixelSizes; + +#ifdef Q_WS_X11 + const char *weightName; + const char *setwidthName; +#endif // Q_WS_X11 +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) + bool antialiased; +#endif + + QtFontSize *pixelSize(unsigned short size, bool = false); +}; + +QtFontStyle::Key::Key(const QString &styleString) + : style(QFont::StyleNormal), weight(QFont::Normal), stretch(0) +{ + weight = getFontWeight(styleString); + + if (styleString.contains(QLatin1String("Italic")) + || styleString.contains(QApplication::translate("QFontDatabase", "Italic"))) + style = QFont::StyleItalic; + else if (styleString.contains(QLatin1String("Oblique")) + || styleString.contains(QApplication::translate("QFontDatabase", "Oblique"))) + style = QFont::StyleOblique; +} + +QtFontSize *QtFontStyle::pixelSize(unsigned short size, bool add) +{ + for (int i = 0; i < count; i++) { + if (pixelSizes[i].pixelSize == size) + return pixelSizes + i; + } + if (!add) + return 0; + + if (!pixelSizes) { + // Most style have only one font size, we avoid waisting memory + QtFontSize *newPixelSizes = (QtFontSize *)malloc(sizeof(QtFontSize)); + Q_CHECK_PTR(newPixelSizes); + pixelSizes = newPixelSizes; + } else if (!(count % 8) || count == 1) { + QtFontSize *newPixelSizes = (QtFontSize *) + realloc(pixelSizes, + (((count+8) >> 3) << 3) * sizeof(QtFontSize)); + Q_CHECK_PTR(newPixelSizes); + pixelSizes = newPixelSizes; + } + pixelSizes[count].pixelSize = size; +#ifdef Q_WS_X11 + pixelSizes[count].count = 0; + pixelSizes[count].encodings = 0; +#endif +#if defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN) + new (&pixelSizes[count].fileName) QByteArray; + pixelSizes[count].fileIndex = 0; +#endif +#if defined(Q_WS_QPA) + pixelSizes[count].handle = 0; +#endif + return pixelSizes + (count++); +} + +struct QtFontFoundry +{ + QtFontFoundry(const QString &n) : name(n), count(0), styles(0) {} + ~QtFontFoundry() { + while (count--) + delete styles[count]; + free(styles); + } + + QString name; + + int count; + QtFontStyle **styles; + QtFontStyle *style(const QtFontStyle::Key &, bool = false); +}; + +QtFontStyle *QtFontFoundry::style(const QtFontStyle::Key &key, bool create) +{ + int pos = 0; + if (count) { + int low = 0; + int high = count; + pos = count / 2; + while (high > low) { + if (styles[pos]->key == key) + return styles[pos]; + if (styles[pos]->key < key) + low = pos + 1; + else + high = pos; + pos = (high + low) / 2; + } + pos = low; + } + if (!create) + return 0; + +// qDebug("adding key (weight=%d, style=%d, oblique=%d stretch=%d) at %d", key.weight, key.style, key.oblique, key.stretch, pos); + if (!(count % 8)) { + QtFontStyle **newStyles = (QtFontStyle **) + realloc(styles, (((count+8) >> 3) << 3) * sizeof(QtFontStyle *)); + Q_CHECK_PTR(newStyles); + styles = newStyles; + } + + QtFontStyle *style = new QtFontStyle(key); + memmove(styles + pos + 1, styles + pos, (count-pos)*sizeof(QtFontStyle *)); + styles[pos] = style; + count++; + return styles[pos]; +} + + +struct QtFontFamily +{ + enum WritingSystemStatus { + Unknown = 0, + Supported = 1, + UnsupportedFT = 2, + UnsupportedXLFD = 4, + Unsupported = UnsupportedFT | UnsupportedXLFD + }; + + QtFontFamily(const QString &n) + : +#ifdef Q_WS_X11 + fixedPitch(true), ftWritingSystemCheck(false), + xlfdLoaded(false), synthetic(false), symbol_checked(false), +#else + fixedPitch(false), +#endif +#ifdef Q_WS_WIN + writingSystemCheck(false), + loaded(false), +#endif +#if !defined(QWS) && defined(Q_OS_MAC) + fixedPitchComputed(false), +#endif + name(n), count(0), foundries(0) +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) + , bogusWritingSystems(false) +#endif +#if defined(Q_WS_QPA) + , askedForFallback(false) +#endif + { + memset(writingSystems, 0, sizeof(writingSystems)); + } + ~QtFontFamily() { + while (count--) + delete foundries[count]; + free(foundries); + } + + bool fixedPitch : 1; +#ifdef Q_WS_X11 + bool ftWritingSystemCheck : 1; + bool xlfdLoaded : 1; + bool synthetic : 1; +#endif +#ifdef Q_WS_WIN + bool writingSystemCheck : 1; + bool loaded : 1; +#endif +#if !defined(QWS) && defined(Q_OS_MAC) + bool fixedPitchComputed : 1; +#endif +#ifdef Q_WS_X11 + bool symbol_checked : 1; +#endif + + QString name; +#if defined(Q_WS_X11) || defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) + QByteArray fontFilename; + int fontFileIndex; +#endif +#ifdef Q_WS_WIN + QString english_name; +#endif + int count; + QtFontFoundry **foundries; + +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) + bool bogusWritingSystems; + QStringList fallbackFamilies; +#endif +#if defined (Q_WS_QPA) + bool askedForFallback; +#endif + unsigned char writingSystems[QFontDatabase::WritingSystemsCount]; + + QtFontFoundry *foundry(const QString &f, bool = false); +}; + +#if !defined(QWS) && defined(Q_OS_MAC) +inline static void qt_mac_get_fixed_pitch(QtFontFamily *f) +{ + if(f && !f->fixedPitchComputed) { + QFontMetrics fm(f->name); + f->fixedPitch = fm.width(QLatin1Char('i')) == fm.width(QLatin1Char('m')); + f->fixedPitchComputed = true; + } +} +#endif + + +QtFontFoundry *QtFontFamily::foundry(const QString &f, bool create) +{ + if (f.isNull() && count == 1) + return foundries[0]; + + for (int i = 0; i < count; i++) { + if (foundries[i]->name.compare(f, Qt::CaseInsensitive) == 0) + return foundries[i]; + } + if (!create) + return 0; + + if (!(count % 8)) { + QtFontFoundry **newFoundries = (QtFontFoundry **) + realloc(foundries, + (((count+8) >> 3) << 3) * sizeof(QtFontFoundry *)); + Q_CHECK_PTR(newFoundries); + foundries = newFoundries; + } + + foundries[count] = new QtFontFoundry(f); + return foundries[count++]; +} + +// ### copied to tools/makeqpf/qpf2.cpp + +// see the Unicode subset bitfields in the MSDN docs +static int requiredUnicodeBits[QFontDatabase::WritingSystemsCount][2] = { + // Any, + { 127, 127 }, + // Latin, + { 0, 127 }, + // Greek, + { 7, 127 }, + // Cyrillic, + { 9, 127 }, + // Armenian, + { 10, 127 }, + // Hebrew, + { 11, 127 }, + // Arabic, + { 13, 127 }, + // Syriac, + { 71, 127 }, + //Thaana, + { 72, 127 }, + //Devanagari, + { 15, 127 }, + //Bengali, + { 16, 127 }, + //Gurmukhi, + { 17, 127 }, + //Gujarati, + { 18, 127 }, + //Oriya, + { 19, 127 }, + //Tamil, + { 20, 127 }, + //Telugu, + { 21, 127 }, + //Kannada, + { 22, 127 }, + //Malayalam, + { 23, 127 }, + //Sinhala, + { 73, 127 }, + //Thai, + { 24, 127 }, + //Lao, + { 25, 127 }, + //Tibetan, + { 70, 127 }, + //Myanmar, + { 74, 127 }, + // Georgian, + { 26, 127 }, + // Khmer, + { 80, 127 }, + // SimplifiedChinese, + { 126, 127 }, + // TraditionalChinese, + { 126, 127 }, + // Japanese, + { 126, 127 }, + // Korean, + { 56, 127 }, + // Vietnamese, + { 0, 127 }, // same as latin1 + // Other, + { 126, 127 }, + // Ogham, + { 78, 127 }, + // Runic, + { 79, 127 }, + // Nko, + { 14, 127 }, +}; + +#define SimplifiedChineseCsbBit 18 +#define TraditionalChineseCsbBit 20 +#define JapaneseCsbBit 17 +#define KoreanCsbBit 21 + +QList qt_determine_writing_systems_from_truetype_bits(quint32 unicodeRange[4], quint32 codePageRange[2]) +{ + QList writingSystems; + bool hasScript = false; + + int i; + for(i = 0; i < QFontDatabase::WritingSystemsCount; i++) { + int bit = requiredUnicodeBits[i][0]; + int index = bit/32; + int flag = 1 << (bit&31); + if (bit != 126 && unicodeRange[index] & flag) { + bit = requiredUnicodeBits[i][1]; + index = bit/32; + + flag = 1 << (bit&31); + if (bit == 127 || unicodeRange[index] & flag) { + writingSystems.append(QFontDatabase::WritingSystem(i)); + hasScript = true; + // qDebug("font %s: index=%d, flag=%8x supports script %d", familyName.latin1(), index, flag, i); + } + } + } + if(codePageRange[0] & (1 << SimplifiedChineseCsbBit)) { + writingSystems.append(QFontDatabase::SimplifiedChinese); + hasScript = true; + //qDebug("font %s supports Simplified Chinese", familyName.latin1()); + } + if(codePageRange[0] & (1 << TraditionalChineseCsbBit)) { + writingSystems.append(QFontDatabase::TraditionalChinese); + hasScript = true; + //qDebug("font %s supports Traditional Chinese", familyName.latin1()); + } + if(codePageRange[0] & (1 << JapaneseCsbBit)) { + writingSystems.append(QFontDatabase::Japanese); + hasScript = true; + //qDebug("font %s supports Japanese", familyName.latin1()); + } + if(codePageRange[0] & (1 << KoreanCsbBit)) { + writingSystems.append(QFontDatabase::Korean); + hasScript = true; + //qDebug("font %s supports Korean", familyName.latin1()); + } + if (!hasScript) + writingSystems.append(QFontDatabase::Symbol); + + return writingSystems; +} + +#if defined(Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) +// class with virtual destructor, derived in qfontdatabase_s60.cpp +class QSymbianFontDatabaseExtras +{ +public: + virtual ~QSymbianFontDatabaseExtras() {} +}; +#endif + +class QFontDatabasePrivate +{ +public: + QFontDatabasePrivate() + : count(0), families(0), reregisterAppFonts(false) +#if defined(Q_WS_QWS) + , stream(0) +#endif +#if defined(Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) + , symbianExtras(0) +#endif +#if defined(Q_WS_WIN) && !defined(QT_NO_DIRECTWRITE) + , directWriteFactory(0) + , directWriteGdiInterop(0) +#endif + { } + + ~QFontDatabasePrivate() { + free(); +#if defined(Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) + if (symbianExtras) + delete symbianExtras; +#endif +#if defined(Q_WS_WIN) && !defined(QT_NO_DIRECTWRITE) + if (directWriteGdiInterop) + directWriteGdiInterop->Release(); + if (directWriteFactory != 0) + directWriteFactory->Release(); +#endif + } + QtFontFamily *family(const QString &f, bool = false); + void free() { + while (count--) + delete families[count]; + ::free(families); + families = 0; + count = 0; + // don't clear the memory fonts! + } + + int count; +#if defined(Q_WS_X11) && !defined(QT_NO_FONTCONFIG) + QString systemLang; +#endif + QtFontFamily **families; + +#if defined(Q_WS_WIN) && !defined(QT_NO_DIRECTWRITE) + IDWriteFactory *directWriteFactory; + IDWriteGdiInterop *directWriteGdiInterop; +#endif + + + struct ApplicationFont { + QString fileName; + QByteArray data; +#if defined(Q_OS_WIN) + HANDLE handle; + bool memoryFont; + QVector signatures; +#elif defined(Q_WS_MAC) + ATSFontContainerRef handle; +#elif defined(Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) + QString temporaryFileName; + TInt screenDeviceFontFileId; + TUid fontStoreFontFileUid; +#endif + QStringList families; + }; + QVector applicationFonts; + int addAppFont(const QByteArray &fontData, const QString &fileName); + bool reregisterAppFonts; + bool isApplicationFont(const QString &fileName); + + void invalidate(); + +#if defined(Q_WS_QWS) + bool loadFromCache(const QString &fontPath); + void addQPF2File(const QByteArray &file); +#endif // Q_WS_QWS +#if defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) + void addFont(const QString &familyname, const char *foundryname, int weight, + bool italic, int pixelSize, const QByteArray &file, int fileIndex, + bool antialiased, + const QList &writingSystems = QList()); +#ifndef QT_NO_FREETYPE + QStringList addTTFile(const QByteArray &file, const QByteArray &fontData = QByteArray()); +#endif // QT_NO_FREETYPE +#endif +#if defined(Q_WS_QWS) + QDataStream *stream; +#elif defined(Q_OS_SYMBIAN) && defined(QT_NO_FREETYPE) + QSymbianFontDatabaseExtras *symbianExtras; +#endif +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) + QStringList fallbackFamilies; +#endif +}; + +void QFontDatabasePrivate::invalidate() +{ + QFontCache::instance()->clear(); + free(); + emit static_cast(QApplication::instance())->fontDatabaseChanged(); +} + +QtFontFamily *QFontDatabasePrivate::family(const QString &f, bool create) +{ + int low = 0; + int high = count; + int pos = count / 2; + int res = 1; + if (count) { + while ((res = families[pos]->name.compare(f, Qt::CaseInsensitive)) && pos != low) { + if (res > 0) + high = pos; + else + low = pos; + pos = (high + low) / 2; + } + if (!res) + return families[pos]; + } + if (!create) + return 0; + + if (res < 0) + pos++; + + // qDebug("adding family %s at %d total=%d", f.latin1(), pos, count); + if (!(count % 8)) { + QtFontFamily **newFamilies = (QtFontFamily **) + realloc(families, + (((count+8) >> 3) << 3) * sizeof(QtFontFamily *)); + Q_CHECK_PTR(newFamilies); + families = newFamilies; + } + + QtFontFamily *family = new QtFontFamily(f); + memmove(families + pos + 1, families + pos, (count-pos)*sizeof(QtFontFamily *)); + families[pos] = family; + count++; + return families[pos]; +} + +#if defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) +void QFontDatabasePrivate::addFont(const QString &familyname, const char *foundryname, int weight, bool italic, int pixelSize, + const QByteArray &file, int fileIndex, bool antialiased, + const QList &writingSystems) +{ +// qDebug() << "Adding font" << familyname << weight << italic << pixelSize << file << fileIndex << antialiased; + QtFontStyle::Key styleKey; + styleKey.style = italic ? QFont::StyleItalic : QFont::StyleNormal; + styleKey.weight = weight; + styleKey.stretch = 100; + QtFontFamily *f = family(familyname, true); + + if (writingSystems.isEmpty()) { + for (int ws = 1; ws < QFontDatabase::WritingSystemsCount; ++ws) { + f->writingSystems[ws] = QtFontFamily::Supported; + } + f->bogusWritingSystems = true; + } else { + for (int i = 0; i < writingSystems.count(); ++i) { + f->writingSystems[writingSystems.at(i)] = QtFontFamily::Supported; + } + } + + QtFontFoundry *foundry = f->foundry(QString::fromLatin1(foundryname), true); + QtFontStyle *style = foundry->style(styleKey, true); + style->smoothScalable = (pixelSize == 0); + style->antialiased = antialiased; + QtFontSize *size = style->pixelSize(pixelSize?pixelSize:SMOOTH_SCALABLE, true); + size->fileName = file; + size->fileIndex = fileIndex; + +#if defined(Q_WS_QWS) + if (stream) { + *stream << familyname << foundry->name << weight << quint8(italic) << pixelSize + << file << fileIndex << quint8(antialiased); + *stream << quint8(writingSystems.count()); + for (int i = 0; i < writingSystems.count(); ++i) + *stream << quint8(writingSystems.at(i)); + } +#else // ..in case of defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) + f->fontFilename = file; + f->fontFileIndex = fileIndex; +#endif +} +#endif + +#if (defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN)) && !defined(QT_NO_FREETYPE) +QStringList QFontDatabasePrivate::addTTFile(const QByteArray &file, const QByteArray &fontData) +{ + QStringList families; + extern FT_Library qt_getFreetype(); + FT_Library library = qt_getFreetype(); + + int index = 0; + int numFaces = 0; + do { + FT_Face face; + FT_Error error; + if (!fontData.isEmpty()) { + error = FT_New_Memory_Face(library, (const FT_Byte *)fontData.constData(), fontData.size(), index, &face); + } else { + error = FT_New_Face(library, file, index, &face); + } + if (error != FT_Err_Ok) { + qDebug() << "FT_New_Face failed with index" << index << ":" << hex << error; + break; + } + numFaces = face->num_faces; + + int weight = QFont::Normal; + bool italic = face->style_flags & FT_STYLE_FLAG_ITALIC; + + if (face->style_flags & FT_STYLE_FLAG_BOLD) + weight = QFont::Bold; + + QList writingSystems; + // detect symbol fonts + for (int i = 0; i < face->num_charmaps; ++i) { + FT_CharMap cm = face->charmaps[i]; + if (cm->encoding == ft_encoding_adobe_custom + || cm->encoding == ft_encoding_symbol) { + writingSystems.append(QFontDatabase::Symbol); + break; + } + } + if (writingSystems.isEmpty()) { + TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(face, ft_sfnt_os2); + if (os2) { + quint32 unicodeRange[4] = { + os2->ulUnicodeRange1, os2->ulUnicodeRange2, os2->ulUnicodeRange3, os2->ulUnicodeRange4 + }; + quint32 codePageRange[2] = { + os2->ulCodePageRange1, os2->ulCodePageRange2 + }; + + writingSystems = qt_determine_writing_systems_from_truetype_bits(unicodeRange, codePageRange); + //for (int i = 0; i < writingSystems.count(); ++i) + // qDebug() << QFontDatabase::writingSystemName(writingSystems.at(i)); + } + } + + QString family = QString::fromAscii(face->family_name); + families.append(family); + addFont(family, /*foundry*/ "", weight, italic, + /*pixelsize*/ 0, file, index, /*antialias*/ true, writingSystems); + + FT_Done_Face(face); + ++index; + } while (index < numFaces); + return families; +} +#endif + +static const int scriptForWritingSystem[] = { + QUnicodeTables::Common, // Any + QUnicodeTables::Latin, // Latin + QUnicodeTables::Greek, // Greek + QUnicodeTables::Cyrillic, // Cyrillic + QUnicodeTables::Armenian, // Armenian + QUnicodeTables::Hebrew, // Hebrew + QUnicodeTables::Arabic, // Arabic + QUnicodeTables::Syriac, // Syriac + QUnicodeTables::Thaana, // Thaana + QUnicodeTables::Devanagari, // Devanagari + QUnicodeTables::Bengali, // Bengali + QUnicodeTables::Gurmukhi, // Gurmukhi + QUnicodeTables::Gujarati, // Gujarati + QUnicodeTables::Oriya, // Oriya + QUnicodeTables::Tamil, // Tamil + QUnicodeTables::Telugu, // Telugu + QUnicodeTables::Kannada, // Kannada + QUnicodeTables::Malayalam, // Malayalam + QUnicodeTables::Sinhala, // Sinhala + QUnicodeTables::Thai, // Thai + QUnicodeTables::Lao, // Lao + QUnicodeTables::Tibetan, // Tibetan + QUnicodeTables::Myanmar, // Myanmar + QUnicodeTables::Georgian, // Georgian + QUnicodeTables::Khmer, // Khmer + QUnicodeTables::Common, // SimplifiedChinese + QUnicodeTables::Common, // TraditionalChinese + QUnicodeTables::Common, // Japanese + QUnicodeTables::Hangul, // Korean + QUnicodeTables::Common, // Vietnamese + QUnicodeTables::Common, // Yi + QUnicodeTables::Common, // Tagalog + QUnicodeTables::Common, // Hanunoo + QUnicodeTables::Common, // Buhid + QUnicodeTables::Common, // Tagbanwa + QUnicodeTables::Common, // Limbu + QUnicodeTables::Common, // TaiLe + QUnicodeTables::Common, // Braille + QUnicodeTables::Common, // Symbol + QUnicodeTables::Ogham, // Ogham + QUnicodeTables::Runic, // Runic + QUnicodeTables::Nko // Nko +}; + +int qt_script_for_writing_system(QFontDatabase::WritingSystem writingSystem) +{ + return scriptForWritingSystem[writingSystem]; +} + + +#if defined Q_WS_QWS || (defined(Q_WS_X11) && !defined(QT_NO_FONTCONFIG)) || defined(Q_WS_WIN) +static inline bool requiresOpenType(int writingSystem) +{ + return ((writingSystem >= QFontDatabase::Syriac && writingSystem <= QFontDatabase::Sinhala) + || writingSystem == QFontDatabase::Khmer || writingSystem == QFontDatabase::Nko); +} +static inline bool scriptRequiresOpenType(int script) +{ + return ((script >= QUnicodeTables::Syriac && script <= QUnicodeTables::Sinhala) + || script == QUnicodeTables::Khmer || script == QUnicodeTables::Nko); +} +#endif + + +/*! + \internal + + This makes sense of the font family name: + + if the family name contains a '[' and a ']', then we take the text + between the square brackets as the foundry, and the text before the + square brackets as the family (ie. "Arial [Monotype]") +*/ +static void parseFontName(const QString &name, QString &foundry, QString &family) +{ + int i = name.indexOf(QLatin1Char('[')); + int li = name.lastIndexOf(QLatin1Char(']')); + if (i >= 0 && li >= 0 && i < li) { + foundry = name.mid(i + 1, li - i - 1); + if (i > 0 && name[i - 1] == QLatin1Char(' ')) + i--; + family = name.left(i); + } else { + foundry.clear(); + family = name; + } + + // capitalize the family/foundry names + bool space = true; + QChar *s = family.data(); + int len = family.length(); + while(len--) { + if (space) *s = s->toUpper(); + space = s->isSpace(); + ++s; + } + + space = true; + s = foundry.data(); + len = foundry.length(); + while(len--) { + if (space) *s = s->toUpper(); + space = s->isSpace(); + ++s; + } +} + + +struct QtFontDesc +{ + inline QtFontDesc() : family(0), foundry(0), style(0), size(0), encoding(0), familyIndex(-1) {} + QtFontFamily *family; + QtFontFoundry *foundry; + QtFontStyle *style; + QtFontSize *size; + QtFontEncoding *encoding; + int familyIndex; +}; + +#if !defined(Q_WS_MAC) +static void match(int script, const QFontDef &request, + const QString &family_name, const QString &foundry_name, int force_encoding_id, + QtFontDesc *desc, const QList &blacklistedFamilies = QList(), bool forceXLFD=false); + +#if defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_WS_QPA) +static void initFontDef(const QtFontDesc &desc, const QFontDef &request, QFontDef *fontDef) +{ + fontDef->family = desc.family->name; + if (! desc.foundry->name.isEmpty() && desc.family->count > 1) { + fontDef->family += QString::fromLatin1(" ["); + fontDef->family += desc.foundry->name; + fontDef->family += QLatin1Char(']'); + } + + if (desc.style->smoothScalable) + fontDef->pixelSize = request.pixelSize; + else if ((desc.style->bitmapScalable && (request.styleStrategy & QFont::PreferMatch))) + fontDef->pixelSize = request.pixelSize; + else + fontDef->pixelSize = desc.size->pixelSize; + + fontDef->styleHint = request.styleHint; + fontDef->styleStrategy = request.styleStrategy; + + fontDef->weight = desc.style->key.weight; + fontDef->style = desc.style->key.style; + fontDef->fixedPitch = desc.family->fixedPitch; + fontDef->stretch = desc.style->key.stretch; + fontDef->ignorePitch = false; +} +#endif +#endif + +#if defined(Q_WS_X11) || defined(Q_WS_WIN) || defined(Q_OS_SYMBIAN) || defined(Q_WS_QPA) +static void getEngineData(const QFontPrivate *d, const QFontCache::Key &key) +{ + // look for the requested font in the engine data cache + d->engineData = QFontCache::instance()->findEngineData(key); + if (!d->engineData) { + // create a new one + d->engineData = new QFontEngineData; + QFontCache::instance()->insertEngineData(key, d->engineData); + } else { + d->engineData->ref.ref(); + } +} +#endif + +static QStringList familyList(const QFontDef &req) +{ + // list of families to try + QStringList family_list; + if (req.family.isEmpty()) + return family_list; + + QStringList list = req.family.split(QLatin1Char(',')); + for (int i = 0; i < list.size(); ++i) { + QString str = list.at(i).trimmed(); + if ((str.startsWith(QLatin1Char('"')) && str.endsWith(QLatin1Char('"'))) + || (str.startsWith(QLatin1Char('\'')) && str.endsWith(QLatin1Char('\'')))) + str = str.mid(1, str.length() - 2); + family_list << str; + } + + // append the substitute list for each family in family_list + QStringList subs_list; + QStringList::ConstIterator it = family_list.constBegin(), end = family_list.constEnd(); + for (; it != end; ++it) + subs_list += QFont::substitutes(*it); +// qDebug() << "adding substs: " << subs_list; + + family_list += subs_list; + + return family_list; +} + +Q_GLOBAL_STATIC(QFontDatabasePrivate, privateDb) +Q_GLOBAL_STATIC_WITH_ARGS(QMutex, fontDatabaseMutex, (QMutex::Recursive)) + +// used in qfontengine_x11.cpp +QMutex *qt_fontdatabase_mutex() +{ + return fontDatabaseMutex(); +} + +QT_BEGIN_INCLUDE_NAMESPACE +#if defined(Q_WS_X11) +# include "qfontdatabase_x11.cpp" +#elif defined(Q_WS_MAC) +# include "qfontdatabase_mac.cpp" +#elif defined(Q_WS_WIN) +# include "qfontdatabase_win.cpp" +#elif defined(Q_WS_QWS) +# include "qfontdatabase_qws.cpp" +#elif defined(Q_WS_QPA) +# include "qfontdatabase_qpa.cpp" +#elif defined(Q_OS_SYMBIAN) +# include "qfontdatabase_s60.cpp" +#endif +#if !defined(Q_WS_X11) +QString QFontDatabase::resolveFontFamilyAlias(const QString &family) +{ + return family; +} +#endif +QT_END_INCLUDE_NAMESPACE + +static QtFontStyle *bestStyle(QtFontFoundry *foundry, const QtFontStyle::Key &styleKey) +{ + int best = 0; + int dist = 0xffff; + + for ( int i = 0; i < foundry->count; i++ ) { + QtFontStyle *style = foundry->styles[i]; + + int d = qAbs( styleKey.weight - style->key.weight ); + + if ( styleKey.stretch != 0 && style->key.stretch != 0 ) { + d += qAbs( styleKey.stretch - style->key.stretch ); + } + + if (styleKey.style != style->key.style) { + if (styleKey.style != QFont::StyleNormal && style->key.style != QFont::StyleNormal) + // one is italic, the other oblique + d += 0x0001; + else + d += 0x1000; + } + + if ( d < dist ) { + best = i; + dist = d; + } + } + + FM_DEBUG( " best style has distance 0x%x", dist ); + return foundry->styles[best]; +} + +#if defined(Q_WS_X11) +static QtFontEncoding *findEncoding(int script, int styleStrategy, + QtFontSize *size, int force_encoding_id) +{ + QtFontEncoding *encoding = 0; + + if (force_encoding_id >= 0) { + encoding = size->encodingID(force_encoding_id); + if (!encoding) + FM_DEBUG(" required encoding_id not available"); + return encoding; + } + + if (styleStrategy & (QFont::OpenGLCompatible | QFont::PreferBitmap)) { + FM_DEBUG(" PreferBitmap and/or OpenGL set, skipping Freetype"); + } else { + encoding = size->encodingID(-1); // -1 == prefer Freetype + if (encoding) + return encoding; + } + + // FT not available, find an XLFD font, trying the default encoding first + encoding = size->encodingID(QFontPrivate::defaultEncodingID); + if (encoding) { + // does it support the requested script? + bool supportsScript = false; + for (int ws = 1; !supportsScript && ws < QFontDatabase::WritingSystemsCount; ++ws) { + if (scriptForWritingSystem[ws] != script) + continue; + supportsScript = writingSystems_for_xlfd_encoding[encoding->encoding][ws]; + } + if (!supportsScript) + encoding = 0; + } + // find the first encoding that supports the requested script + for (int ws = 1; !encoding && ws < QFontDatabase::WritingSystemsCount; ++ws) { + if (scriptForWritingSystem[ws] != script) + continue; + for (int x = 0; !encoding && x < size->count; ++x) { + const int enc = size->encodings[x].encoding; + if (writingSystems_for_xlfd_encoding[enc][ws]) + encoding = size->encodings + x; + } + } + + return encoding; +} +#endif // Q_WS_X11 + +#if !defined(Q_WS_MAC) +static +unsigned int bestFoundry(int script, unsigned int score, int styleStrategy, + const QtFontFamily *family, const QString &foundry_name, + QtFontStyle::Key styleKey, int pixelSize, char pitch, + QtFontDesc *desc, int force_encoding_id) +{ + Q_UNUSED(force_encoding_id); + Q_UNUSED(script); + Q_UNUSED(pitch); + + desc->foundry = 0; + desc->style = 0; + desc->size = 0; + desc->encoding = 0; + + + FM_DEBUG(" REMARK: looking for best foundry for family '%s' [%d]", family->name.toLatin1().constData(), family->count); + + for (int x = 0; x < family->count; ++x) { + QtFontFoundry *foundry = family->foundries[x]; + if (!foundry_name.isEmpty() && foundry->name.compare(foundry_name, Qt::CaseInsensitive) != 0) + continue; + + FM_DEBUG(" looking for matching style in foundry '%s' %d", + foundry->name.isEmpty() ? "-- none --" : foundry->name.toLatin1().constData(), foundry->count); + + QtFontStyle *style = bestStyle(foundry, styleKey); + + if (!style->smoothScalable && (styleStrategy & QFont::ForceOutline)) { + FM_DEBUG(" ForceOutline set, but not smoothly scalable"); + continue; + } + + int px = -1; + QtFontSize *size = 0; + + // 1. see if we have an exact matching size + if (!(styleStrategy & QFont::ForceOutline)) { + size = style->pixelSize(pixelSize); + if (size) { + FM_DEBUG(" found exact size match (%d pixels)", size->pixelSize); + px = size->pixelSize; + } + } + + // 2. see if we have a smoothly scalable font + if (!size && style->smoothScalable && ! (styleStrategy & QFont::PreferBitmap)) { + size = style->pixelSize(SMOOTH_SCALABLE); + if (size) { + FM_DEBUG(" found smoothly scalable font (%d pixels)", pixelSize); + px = pixelSize; + } + } + + // 3. see if we have a bitmap scalable font + if (!size && style->bitmapScalable && (styleStrategy & QFont::PreferMatch)) { + size = style->pixelSize(0); + if (size) { + FM_DEBUG(" found bitmap scalable font (%d pixels)", pixelSize); + px = pixelSize; + } + } + +#ifdef Q_WS_X11 + QtFontEncoding *encoding = 0; +#endif + + // 4. find closest size match + if (! size) { + unsigned int distance = ~0u; + for (int x = 0; x < style->count; ++x) { +#ifdef Q_WS_X11 + encoding = + findEncoding(script, styleStrategy, style->pixelSizes + x, force_encoding_id); + if (!encoding) { + FM_DEBUG(" size %3d does not support the script we want", + style->pixelSizes[x].pixelSize); + continue; + } +#endif + + unsigned int d; + if (style->pixelSizes[x].pixelSize < pixelSize) { + // penalize sizes that are smaller than the + // requested size, due to truncation from floating + // point to integer conversions + d = pixelSize - style->pixelSizes[x].pixelSize + 1; + } else { + d = style->pixelSizes[x].pixelSize - pixelSize; + } + + if (d < distance) { + distance = d; + size = style->pixelSizes + x; + FM_DEBUG(" best size so far: %3d (%d)", size->pixelSize, pixelSize); + } + } + + if (!size) { + FM_DEBUG(" no size supports the script we want"); + continue; + } + + if (style->bitmapScalable && ! (styleStrategy & QFont::PreferQuality) && + (distance * 10 / pixelSize) >= 2) { + // the closest size is not close enough, go ahead and + // use a bitmap scaled font + size = style->pixelSize(0); + px = pixelSize; + } else { + px = size->pixelSize; + } + } + +#ifdef Q_WS_X11 + if (size) { + encoding = findEncoding(script, styleStrategy, size, force_encoding_id); + if (!encoding) size = 0; + } + if (! encoding) { + FM_DEBUG(" foundry doesn't support the script we want"); + continue; + } +#endif // Q_WS_X11 + + unsigned int this_score = 0x0000; + enum { + PitchMismatch = 0x4000, + StyleMismatch = 0x2000, + BitmapScaledPenalty = 0x1000, + EncodingMismatch = 0x0002, + XLFDPenalty = 0x0001 + }; +#ifdef Q_WS_X11 + if (encoding->encoding != -1) { + this_score += XLFDPenalty; + if (encoding->encoding != QFontPrivate::defaultEncodingID) + this_score += EncodingMismatch; + } + if (pitch != '*') { + if (!(pitch == 'm' && encoding->pitch == 'c') && pitch != encoding->pitch) + this_score += PitchMismatch; + } +#else + if (pitch != '*') { +#if !defined(QWS) && defined(Q_OS_MAC) + qt_mac_get_fixed_pitch(const_cast(family)); +#endif + if ((pitch == 'm' && !family->fixedPitch) + || (pitch == 'p' && family->fixedPitch)) + this_score += PitchMismatch; + } +#endif + if (styleKey != style->key) + this_score += StyleMismatch; + if (!style->smoothScalable && px != size->pixelSize) // bitmap scaled + this_score += BitmapScaledPenalty; + if (px != pixelSize) // close, but not exact, size match + this_score += qAbs(px - pixelSize); + + if (this_score < score) { + FM_DEBUG(" found a match: score %x best score so far %x", + this_score, score); + + score = this_score; + desc->foundry = foundry; + desc->style = style; + desc->size = size; +#ifdef Q_WS_X11 + desc->encoding = encoding; +#endif // Q_WS_X11 + } else { + FM_DEBUG(" score %x no better than best %x", this_score, score); + } + } + + return score; +} +#endif + +#if !defined(Q_WS_MAC) +/*! + \internal + + Tries to find the best match for a given request and family/foundry +*/ +static void match(int script, const QFontDef &request, + const QString &family_name, const QString &foundry_name, int force_encoding_id, + QtFontDesc *desc, const QList &blacklistedFamilies, bool forceXLFD) +{ + Q_UNUSED(force_encoding_id); + + QtFontStyle::Key styleKey; + styleKey.style = request.style; + styleKey.weight = request.weight; + styleKey.stretch = request.stretch; + char pitch = request.ignorePitch ? '*' : request.fixedPitch ? 'm' : 'p'; + + + FM_DEBUG("QFontDatabase::match\n" + " request:\n" + " family: %s [%s], script: %d\n" + " weight: %d, style: %d\n" + " stretch: %d\n" + " pixelSize: %g\n" + " pitch: %c", + family_name.isEmpty() ? "-- first in script --" : family_name.toLatin1().constData(), + foundry_name.isEmpty() ? "-- any --" : foundry_name.toLatin1().constData(), + script, request.weight, request.style, request.stretch, request.pixelSize, pitch); +#if defined(FONT_MATCH_DEBUG) && defined(Q_WS_X11) + if (force_encoding_id >= 0) { + FM_DEBUG(" required encoding: %d", force_encoding_id); + } +#endif + + desc->family = 0; + desc->foundry = 0; + desc->style = 0; + desc->size = 0; + desc->encoding = 0; + desc->familyIndex = -1; + + unsigned int score = ~0u; + +#ifdef Q_WS_X11 + load(family_name, script, forceXLFD); +#else + Q_UNUSED(forceXLFD); + load(family_name, script); +#endif + + QFontDatabasePrivate *db = privateDb(); + for (int x = 0; x < db->count; ++x) { + if (blacklistedFamilies.contains(x)) + continue; + QtFontDesc test; + test.family = db->families[x]; + test.familyIndex = x; + + if (!family_name.isEmpty() + && test.family->name.compare(family_name, Qt::CaseInsensitive) != 0 +#ifdef Q_WS_WIN + && test.family->english_name.compare(family_name, Qt::CaseInsensitive) != 0 +#endif + ) + continue; + + if (family_name.isEmpty()) + load(test.family->name, script); + + uint score_adjust = 0; + + bool supported = (script == QUnicodeTables::Common); + for (int ws = 1; !supported && ws < QFontDatabase::WritingSystemsCount; ++ws) { + if (scriptForWritingSystem[ws] != script) + continue; + if (test.family->writingSystems[ws] & QtFontFamily::Supported) + supported = true; + } + if (!supported) { + // family not supported in the script we want + continue; + } + + // as we know the script is supported, we can be sure + // to find a matching font here. + unsigned int newscore = + bestFoundry(script, score, request.styleStrategy, + test.family, foundry_name, styleKey, request.pixelSize, pitch, + &test, force_encoding_id); + if (test.foundry == 0) { + // the specific foundry was not found, so look for + // any foundry matching our requirements + newscore = bestFoundry(script, score, request.styleStrategy, test.family, + QString(), styleKey, request.pixelSize, + pitch, &test, force_encoding_id); + } + newscore += score_adjust; + + if (newscore < score) { + score = newscore; + *desc = test; + } + if (newscore < 10) // xlfd instead of FT... just accept it + break; + } +} +#endif + +static QString styleStringHelper(int weight, QFont::Style style) +{ + QString result; + if (weight >= QFont::Black) + result = QApplication::translate("QFontDatabase", "Black"); + else if (weight >= QFont::Bold) + result = QApplication::translate("QFontDatabase", "Bold"); + else if (weight >= QFont::DemiBold) + result = QApplication::translate("QFontDatabase", "Demi Bold"); + else if (weight < QFont::Normal) + result = QApplication::translate("QFontDatabase", "Light"); + + if (style == QFont::StyleItalic) + result += QLatin1Char(' ') + QApplication::translate("QFontDatabase", "Italic"); + else if (style == QFont::StyleOblique) + result += QLatin1Char(' ') + QApplication::translate("QFontDatabase", "Oblique"); + + if (result.isEmpty()) + result = QApplication::translate("QFontDatabase", "Normal"); + + return result.simplified(); +} + +/*! + Returns a string that describes the style of the \a font. For + example, "Bold Italic", "Bold", "Italic" or "Normal". An empty + string may be returned. +*/ +QString QFontDatabase::styleString(const QFont &font) +{ + return styleStringHelper(font.weight(), font.style()); +} + +/*! + Returns a string that describes the style of the \a fontInfo. For + example, "Bold Italic", "Bold", "Italic" or "Normal". An empty + string may be returned. +*/ +QString QFontDatabase::styleString(const QFontInfo &fontInfo) +{ + return styleStringHelper(fontInfo.weight(), fontInfo.style()); +} + + +/*! + \class QFontDatabase + \threadsafe + + \brief The QFontDatabase class provides information about the fonts available in the underlying window system. + + \ingroup appearance + + The most common uses of this class are to query the database for + the list of font families() and for the pointSizes() and styles() + that are available for each family. An alternative to pointSizes() + is smoothSizes() which returns the sizes at which a given family + and style will look attractive. + + If the font family is available from two or more foundries the + foundry name is included in the family name; for example: + "Helvetica [Adobe]" and "Helvetica [Cronyx]". When you specify a + family, you can either use the old hyphenated "foundry-family" + format or the bracketed "family [foundry]" format; for example: + "Cronyx-Helvetica" or "Helvetica [Cronyx]". If the family has a + foundry it is always returned using the bracketed format, as is + the case with the value returned by families(). + + The font() function returns a QFont given a family, style and + point size. + + A family and style combination can be checked to see if it is + italic() or bold(), and to retrieve its weight(). Similarly we can + call isBitmapScalable(), isSmoothlyScalable(), isScalable() and + isFixedPitch(). + + Use the styleString() to obtain a text version of a style. + + The QFontDatabase class also supports some static functions, for + example, standardSizes(). You can retrieve the description of a + writing system using writingSystemName(), and a sample of + characters in a writing system with writingSystemSample(). + + Example: + + \snippet doc/src/snippets/qfontdatabase/main.cpp 0 + \snippet doc/src/snippets/qfontdatabase/main.cpp 1 + + This example gets the list of font families, the list of + styles for each family, and the point sizes that are available for + each combination of family and style, displaying this information + in a tree view. + + \sa QFont, QFontInfo, QFontMetrics, QFontComboBox, {Character Map Example} +*/ + +/*! + Creates a font database object. +*/ +QFontDatabase::QFontDatabase() +{ + QMutexLocker locker(fontDatabaseMutex()); + createDatabase(); + d = privateDb(); +} + +/*! + \enum QFontDatabase::WritingSystem + + \value Any + \value Latin + \value Greek + \value Cyrillic + \value Armenian + \value Hebrew + \value Arabic + \value Syriac + \value Thaana + \value Devanagari + \value Bengali + \value Gurmukhi + \value Gujarati + \value Oriya + \value Tamil + \value Telugu + \value Kannada + \value Malayalam + \value Sinhala + \value Thai + \value Lao + \value Tibetan + \value Myanmar + \value Georgian + \value Khmer + \value SimplifiedChinese + \value TraditionalChinese + \value Japanese + \value Korean + \value Vietnamese + \value Symbol + \value Other (the same as Symbol) + \value Ogham + \value Runic + \value Nko + + \omitvalue WritingSystemsCount +*/ + +/*! + Returns a sorted list of the available writing systems. This is + list generated from information about all installed fonts on the + system. + + \sa families() +*/ +QList QFontDatabase::writingSystems() const +{ + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(); +#ifdef Q_WS_X11 + checkSymbolFonts(); +#endif + + QList list; + for (int i = 0; i < d->count; ++i) { + QtFontFamily *family = d->families[i]; + if (family->count == 0) + continue; + for (int x = Latin; x < WritingSystemsCount; ++x) { + const WritingSystem writingSystem = WritingSystem(x); + if (!(family->writingSystems[writingSystem] & QtFontFamily::Supported)) + continue; + if (!list.contains(writingSystem)) + list.append(writingSystem); + } + } + qSort(list); + return list; +} + + +/*! + Returns a sorted list of the writing systems supported by a given + font \a family. + + \sa families() +*/ +QList QFontDatabase::writingSystems(const QString &family) const +{ + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(); +#ifdef Q_WS_X11 + checkSymbolFonts(familyName); +#endif + + QList list; + QtFontFamily *f = d->family(familyName); + if (!f || f->count == 0) + return list; + + for (int x = Latin; x < WritingSystemsCount; ++x) { + const WritingSystem writingSystem = WritingSystem(x); + if (f->writingSystems[writingSystem] & QtFontFamily::Supported) + list.append(writingSystem); + } + return list; +} + + +/*! + Returns a sorted list of the available font families which support + the \a writingSystem. + + If a family exists in several foundries, the returned name for + that font is in the form "family [foundry]". Examples: "Times + [Adobe]", "Times [Cronyx]", "Palatino". + + \sa writingSystems() +*/ +QStringList QFontDatabase::families(WritingSystem writingSystem) const +{ + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(); +#ifdef Q_WS_X11 + if (writingSystem != Any) + checkSymbolFonts(); +#endif + + QStringList flist; + for (int i = 0; i < d->count; i++) { + QtFontFamily *f = d->families[i]; + if (f->count == 0) + continue; + if (writingSystem != Any && (f->writingSystems[writingSystem] != QtFontFamily::Supported)) + continue; + if (f->count == 1) { + flist.append(f->name); + } else { + for (int j = 0; j < f->count; j++) { + QString str = f->name; + QString foundry = f->foundries[j]->name; + if (!foundry.isEmpty()) { + str += QLatin1String(" ["); + str += foundry; + str += QLatin1Char(']'); + } + flist.append(str); + } + } + } + return flist; +} + +/*! + Returns a list of the styles available for the font family \a + family. Some example styles: "Light", "Light Italic", "Bold", + "Oblique", "Demi". The list may be empty. + + \sa families() +*/ +QStringList QFontDatabase::styles(const QString &family) const +{ + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QStringList l; + QtFontFamily *f = d->family(familyName); + if (!f) + return l; + + QtFontFoundry allStyles(foundryName); + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) { + QtFontStyle::Key ke(foundry->styles[k]->key); + ke.stretch = 0; + allStyles.style(ke, true); + } + } + } + + for (int i = 0; i < allStyles.count; i++) + l.append(styleStringHelper(allStyles.styles[i]->key.weight, (QFont::Style)allStyles.styles[i]->key.style)); + return l; +} + +/*! + Returns true if the font that has family \a family and style \a + style is fixed pitch; otherwise returns false. +*/ + +bool QFontDatabase::isFixedPitch(const QString &family, + const QString &style) const +{ + Q_UNUSED(style); + + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontFamily *f = d->family(familyName); +#if !defined(QWS) && defined(Q_OS_MAC) + qt_mac_get_fixed_pitch(f); +#endif + return (f && f->fixedPitch); +} + +/*! + Returns true if the font that has family \a family and style \a + style is a scalable bitmap font; otherwise returns false. Scaling + a bitmap font usually produces an unattractive hardly readable + result, because the pixels of the font are scaled. If you need to + scale a bitmap font it is better to scale it to one of the fixed + sizes returned by smoothSizes(). + + \sa isScalable(), isSmoothlyScalable() +*/ +bool QFontDatabase::isBitmapScalable(const QString &family, + const QString &style) const +{ + bool bitmapScalable = false; + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontStyle::Key styleKey(style); + + QtFontFamily *f = d->family(familyName); + if (!f) return bitmapScalable; + + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) + if ((style.isEmpty() || foundry->styles[k]->key == styleKey) + && foundry->styles[k]->bitmapScalable && !foundry->styles[k]->smoothScalable) { + bitmapScalable = true; + goto end; + } + } + } + end: + return bitmapScalable; +} + + +/*! + Returns true if the font that has family \a family and style \a + style is smoothly scalable; otherwise returns false. If this + function returns true, it's safe to scale this font to any size, + and the result will always look attractive. + + \sa isScalable(), isBitmapScalable() +*/ +bool QFontDatabase::isSmoothlyScalable(const QString &family, const QString &style) const +{ + bool smoothScalable = false; + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontStyle::Key styleKey(style); + + QtFontFamily *f = d->family(familyName); + if (!f) return smoothScalable; + + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) + if ((style.isEmpty() || foundry->styles[k]->key == styleKey) && foundry->styles[k]->smoothScalable) { + smoothScalable = true; + goto end; + } + } + } + end: + return smoothScalable; +} + +/*! + Returns true if the font that has family \a family and style \a + style is scalable; otherwise returns false. + + \sa isBitmapScalable(), isSmoothlyScalable() +*/ +bool QFontDatabase::isScalable(const QString &family, + const QString &style) const +{ + QMutexLocker locker(fontDatabaseMutex()); + if (isSmoothlyScalable(family, style)) + return true; + return isBitmapScalable(family, style); +} + + +/*! + Returns a list of the point sizes available for the font that has + family \a family and style \a style. The list may be empty. + + \sa smoothSizes(), standardSizes() +*/ +QList QFontDatabase::pointSizes(const QString &family, + const QString &style) +{ +#if defined(Q_WS_WIN) + // windows and macosx are always smoothly scalable + Q_UNUSED(family); + Q_UNUSED(style); + return standardSizes(); +#else + bool smoothScalable = false; + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontStyle::Key styleKey(style); + + QList sizes; + + QtFontFamily *fam = d->family(familyName); + if (!fam) return sizes; + + +#ifdef Q_WS_X11 + int dpi = QX11Info::appDpiY(); +#else + const int dpi = qt_defaultDpiY(); // embedded +#endif + + for (int j = 0; j < fam->count; j++) { + QtFontFoundry *foundry = fam->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + QtFontStyle *style = foundry->style(styleKey); + if (!style) continue; + + if (style->smoothScalable) { + smoothScalable = true; + goto end; + } + for (int l = 0; l < style->count; l++) { + const QtFontSize *size = style->pixelSizes + l; + + if (size->pixelSize != 0 && size->pixelSize != USHRT_MAX) { + const uint pointSize = qRound(size->pixelSize * 72.0 / dpi); + if (! sizes.contains(pointSize)) + sizes.append(pointSize); + } + } + } + } + end: + if (smoothScalable) + return standardSizes(); + + qSort(sizes); + return sizes; +#endif +} + +/*! + Returns a QFont object that has family \a family, style \a style + and point size \a pointSize. If no matching font could be created, + a QFont object that uses the application's default font is + returned. +*/ +QFont QFontDatabase::font(const QString &family, const QString &style, + int pointSize) const +{ + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontFoundry allStyles(foundryName); + QtFontFamily *f = d->family(familyName); + if (!f) return QApplication::font(); + + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) + allStyles.style(foundry->styles[k]->key, true); + } + } + + QtFontStyle::Key styleKey(style); + QtFontStyle *s = bestStyle(&allStyles, styleKey); + + if (!s) // no styles found? + return QApplication::font(); + QFont fnt(family, pointSize, s->key.weight); + fnt.setStyle((QFont::Style)s->key.style); + return fnt; +} + + +/*! + Returns the point sizes of a font that has family \a family and + style \a style that will look attractive. The list may be empty. + For non-scalable fonts and bitmap scalable fonts, this function + is equivalent to pointSizes(). + + \sa pointSizes(), standardSizes() +*/ +QList QFontDatabase::smoothSizes(const QString &family, + const QString &style) +{ +#ifdef Q_WS_WIN + Q_UNUSED(family); + Q_UNUSED(style); + return QFontDatabase::standardSizes(); +#else + bool smoothScalable = false; + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontStyle::Key styleKey(style); + + QList sizes; + + QtFontFamily *fam = d->family(familyName); + if (!fam) + return sizes; + +#ifdef Q_WS_X11 + int dpi = QX11Info::appDpiY(); +#else + const int dpi = qt_defaultDpiY(); // embedded +#endif + + for (int j = 0; j < fam->count; j++) { + QtFontFoundry *foundry = fam->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + QtFontStyle *style = foundry->style(styleKey); + if (!style) continue; + + if (style->smoothScalable) { + smoothScalable = true; + goto end; + } + for (int l = 0; l < style->count; l++) { + const QtFontSize *size = style->pixelSizes + l; + + if (size->pixelSize != 0 && size->pixelSize != USHRT_MAX) { + const uint pointSize = qRound(size->pixelSize * 72.0 / dpi); + if (! sizes.contains(pointSize)) + sizes.append(pointSize); + } + } + } + } + end: + if (smoothScalable) + return QFontDatabase::standardSizes(); + + qSort(sizes); + return sizes; +#endif +} + + +/*! + Returns a list of standard font sizes. + + \sa smoothSizes(), pointSizes() +*/ +QList QFontDatabase::standardSizes() +{ + QList ret; + static const unsigned short standard[] = + { 6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72, 0 }; + const unsigned short *sizes = standard; + while (*sizes) ret << *sizes++; + return ret; +} + + +/*! + Returns true if the font that has family \a family and style \a + style is italic; otherwise returns false. + + \sa weight(), bold() +*/ +bool QFontDatabase::italic(const QString &family, const QString &style) const +{ + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontFoundry allStyles(foundryName); + QtFontFamily *f = d->family(familyName); + if (!f) return false; + + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) + allStyles.style(foundry->styles[k]->key, true); + } + } + + QtFontStyle::Key styleKey(style); + QtFontStyle *s = allStyles.style(styleKey); + return s && s->key.style == QFont::StyleItalic; +} + + +/*! + Returns true if the font that has family \a family and style \a + style is bold; otherwise returns false. + + \sa italic(), weight() +*/ +bool QFontDatabase::bold(const QString &family, + const QString &style) const +{ + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontFoundry allStyles(foundryName); + QtFontFamily *f = d->family(familyName); + if (!f) return false; + + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || + foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) + allStyles.style(foundry->styles[k]->key, true); + } + } + + QtFontStyle::Key styleKey(style); + QtFontStyle *s = allStyles.style(styleKey); + return s && s->key.weight >= QFont::Bold; +} + + +/*! + Returns the weight of the font that has family \a family and style + \a style. If there is no such family and style combination, + returns -1. + + \sa italic(), bold() +*/ +int QFontDatabase::weight(const QString &family, + const QString &style) const +{ + QString familyName, foundryName; + parseFontName(family, foundryName, familyName); + + QMutexLocker locker(fontDatabaseMutex()); + + QT_PREPEND_NAMESPACE(load)(familyName); + + QtFontFoundry allStyles(foundryName); + QtFontFamily *f = d->family(familyName); + if (!f) return -1; + + for (int j = 0; j < f->count; j++) { + QtFontFoundry *foundry = f->foundries[j]; + if (foundryName.isEmpty() || + foundry->name.compare(foundryName, Qt::CaseInsensitive) == 0) { + for (int k = 0; k < foundry->count; k++) + allStyles.style(foundry->styles[k]->key, true); + } + } + + QtFontStyle::Key styleKey(style); + QtFontStyle *s = allStyles.style(styleKey); + return s ? s->key.weight : -1; +} + + +/*! + Returns the names the \a writingSystem (e.g. for displaying to the + user in a dialog). +*/ +QString QFontDatabase::writingSystemName(WritingSystem writingSystem) +{ + const char *name = 0; + switch (writingSystem) { + case Any: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Any"); + break; + case Latin: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Latin"); + break; + case Greek: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Greek"); + break; + case Cyrillic: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Cyrillic"); + break; + case Armenian: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Armenian"); + break; + case Hebrew: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Hebrew"); + break; + case Arabic: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Arabic"); + break; + case Syriac: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Syriac"); + break; + case Thaana: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Thaana"); + break; + case Devanagari: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Devanagari"); + break; + case Bengali: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Bengali"); + break; + case Gurmukhi: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Gurmukhi"); + break; + case Gujarati: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Gujarati"); + break; + case Oriya: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Oriya"); + break; + case Tamil: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Tamil"); + break; + case Telugu: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Telugu"); + break; + case Kannada: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Kannada"); + break; + case Malayalam: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Malayalam"); + break; + case Sinhala: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Sinhala"); + break; + case Thai: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Thai"); + break; + case Lao: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Lao"); + break; + case Tibetan: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Tibetan"); + break; + case Myanmar: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Myanmar"); + break; + case Georgian: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Georgian"); + break; + case Khmer: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Khmer"); + break; + case SimplifiedChinese: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Simplified Chinese"); + break; + case TraditionalChinese: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Traditional Chinese"); + break; + case Japanese: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Japanese"); + break; + case Korean: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Korean"); + break; + case Vietnamese: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Vietnamese"); + break; + case Symbol: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Symbol"); + break; + case Ogham: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Ogham"); + break; + case Runic: + name = QT_TRANSLATE_NOOP("QFontDatabase", "Runic"); + break; + case Nko: + name = QT_TRANSLATE_NOOP("QFontDatabase", "N'Ko"); + break; + default: + Q_ASSERT_X(false, "QFontDatabase::writingSystemName", "invalid 'writingSystem' parameter"); + break; + } + return QApplication::translate("QFontDatabase", name); +} + + +/*! + Returns a string with sample characters from \a writingSystem. +*/ +QString QFontDatabase::writingSystemSample(WritingSystem writingSystem) +{ + QString sample; + switch (writingSystem) { + case Any: + case Symbol: + // show only ascii characters + sample += QLatin1String("AaBbzZ"); + break; + case Latin: + // This is cheating... we only show latin-1 characters so that we don't + // end up loading lots of fonts - at least on X11... + sample = QLatin1String("Aa"); + sample += QChar(0x00C3); + sample += QChar(0x00E1); + sample += QLatin1String("Zz"); + break; + case Greek: + sample += QChar(0x0393); + sample += QChar(0x03B1); + sample += QChar(0x03A9); + sample += QChar(0x03C9); + break; + case Cyrillic: + sample += QChar(0x0414); + sample += QChar(0x0434); + sample += QChar(0x0436); + sample += QChar(0x044f); + break; + case Armenian: + sample += QChar(0x053f); + sample += QChar(0x054f); + sample += QChar(0x056f); + sample += QChar(0x057f); + break; + case Hebrew: + sample += QChar(0x05D0); + sample += QChar(0x05D1); + sample += QChar(0x05D2); + sample += QChar(0x05D3); + break; + case Arabic: + sample += QChar(0x0628); + sample += QChar(0x0629); + sample += QChar(0x062A); + sample += QChar(0x063A); + break; + case Syriac: + sample += QChar(0x0715); + sample += QChar(0x0725); + sample += QChar(0x0716); + sample += QChar(0x0726); + break; + case Thaana: + sample += QChar(0x0784); + sample += QChar(0x0794); + sample += QChar(0x078c); + sample += QChar(0x078d); + break; + case Devanagari: + sample += QChar(0x0905); + sample += QChar(0x0915); + sample += QChar(0x0925); + sample += QChar(0x0935); + break; + case Bengali: + sample += QChar(0x0986); + sample += QChar(0x0996); + sample += QChar(0x09a6); + sample += QChar(0x09b6); + break; + case Gurmukhi: + sample += QChar(0x0a05); + sample += QChar(0x0a15); + sample += QChar(0x0a25); + sample += QChar(0x0a35); + break; + case Gujarati: + sample += QChar(0x0a85); + sample += QChar(0x0a95); + sample += QChar(0x0aa5); + sample += QChar(0x0ab5); + break; + case Oriya: + sample += QChar(0x0b06); + sample += QChar(0x0b16); + sample += QChar(0x0b2b); + sample += QChar(0x0b36); + break; + case Tamil: + sample += QChar(0x0b89); + sample += QChar(0x0b99); + sample += QChar(0x0ba9); + sample += QChar(0x0bb9); + break; + case Telugu: + sample += QChar(0x0c05); + sample += QChar(0x0c15); + sample += QChar(0x0c25); + sample += QChar(0x0c35); + break; + case Kannada: + sample += QChar(0x0c85); + sample += QChar(0x0c95); + sample += QChar(0x0ca5); + sample += QChar(0x0cb5); + break; + case Malayalam: + sample += QChar(0x0d05); + sample += QChar(0x0d15); + sample += QChar(0x0d25); + sample += QChar(0x0d35); + break; + case Sinhala: + sample += QChar(0x0d90); + sample += QChar(0x0da0); + sample += QChar(0x0db0); + sample += QChar(0x0dc0); + break; + case Thai: + sample += QChar(0x0e02); + sample += QChar(0x0e12); + sample += QChar(0x0e22); + sample += QChar(0x0e32); + break; + case Lao: + sample += QChar(0x0e8d); + sample += QChar(0x0e9d); + sample += QChar(0x0ead); + sample += QChar(0x0ebd); + break; + case Tibetan: + sample += QChar(0x0f00); + sample += QChar(0x0f01); + sample += QChar(0x0f02); + sample += QChar(0x0f03); + break; + case Myanmar: + sample += QChar(0x1000); + sample += QChar(0x1001); + sample += QChar(0x1002); + sample += QChar(0x1003); + break; + case Georgian: + sample += QChar(0x10a0); + sample += QChar(0x10b0); + sample += QChar(0x10c0); + sample += QChar(0x10d0); + break; + case Khmer: + sample += QChar(0x1780); + sample += QChar(0x1790); + sample += QChar(0x17b0); + sample += QChar(0x17c0); + break; + case SimplifiedChinese: + sample += QChar(0x4e2d); + sample += QChar(0x6587); + sample += QChar(0x8303); + sample += QChar(0x4f8b); + break; + case TraditionalChinese: + sample += QChar(0x4e2d); + sample += QChar(0x6587); + sample += QChar(0x7bc4); + sample += QChar(0x4f8b); + break; + case Japanese: + sample += QChar(0x30b5); + sample += QChar(0x30f3); + sample += QChar(0x30d7); + sample += QChar(0x30eb); + sample += QChar(0x3067); + sample += QChar(0x3059); + break; + case Korean: + sample += QChar(0xac00); + sample += QChar(0xac11); + sample += QChar(0xac1a); + sample += QChar(0xac2f); + break; + case Vietnamese: + { + static const char vietnameseUtf8[] = { + char(0xef), char(0xbb), char(0xbf), char(0xe1), char(0xbb), char(0x97), + char(0xe1), char(0xbb), char(0x99), + char(0xe1), char(0xbb), char(0x91), + char(0xe1), char(0xbb), char(0x93), + }; + sample += QString::fromUtf8(vietnameseUtf8, sizeof(vietnameseUtf8)); + break; + } + case Ogham: + sample += QChar(0x1681); + sample += QChar(0x1682); + sample += QChar(0x1683); + sample += QChar(0x1684); + break; + case Runic: + sample += QChar(0x16a0); + sample += QChar(0x16a1); + sample += QChar(0x16a2); + sample += QChar(0x16a3); + break; + case Nko: + sample += QChar(0x7ca); + sample += QChar(0x7cb); + sample += QChar(0x7cc); + sample += QChar(0x7cd); + break; + default: + break; + } + return sample; +} + + +void QFontDatabase::parseFontName(const QString &name, QString &foundry, QString &family) +{ + QT_PREPEND_NAMESPACE(parseFontName)(name, foundry, family); +} + +void QFontDatabase::createDatabase() +{ initializeDb(); } + +// used from qfontengine_ft.cpp +Q_GUI_EXPORT QByteArray qt_fontdata_from_index(int index) +{ + QMutexLocker locker(fontDatabaseMutex()); + return privateDb()->applicationFonts.value(index).data; +} + +int QFontDatabasePrivate::addAppFont(const QByteArray &fontData, const QString &fileName) +{ + QFontDatabasePrivate::ApplicationFont font; + font.data = fontData; + font.fileName = fileName; + + int i; + for (i = 0; i < applicationFonts.count(); ++i) + if (applicationFonts.at(i).families.isEmpty()) + break; + if (i >= applicationFonts.count()) { + applicationFonts.append(ApplicationFont()); + i = applicationFonts.count() - 1; + } + + if (font.fileName.isEmpty() && !fontData.isEmpty()) + font.fileName = QString::fromLatin1(":qmemoryfonts/") + QString::number(i); + + registerFont(&font); + if (font.families.isEmpty()) + return -1; + + applicationFonts[i] = font; + + invalidate(); + return i; +} + +bool QFontDatabasePrivate::isApplicationFont(const QString &fileName) +{ + for (int i = 0; i < applicationFonts.count(); ++i) + if (applicationFonts.at(i).fileName == fileName) + return true; + return false; +} + +/*! + \since 4.2 + + Loads the font from the file specified by \a fileName and makes it available to + the application. An ID is returned that can be used to remove the font again + with removeApplicationFont() or to retrieve the list of family names contained + in the font. + + The function returns -1 if the font could not be loaded. + + Currently only TrueType fonts, TrueType font collections, and OpenType fonts are + supported. + + \note Adding application fonts on Unix/X11 platforms without fontconfig is + currently not supported. + + \note On Symbian, the font family names get truncated to a length of 20 characters. + + \sa addApplicationFontFromData(), applicationFontFamilies(), removeApplicationFont() +*/ +int QFontDatabase::addApplicationFont(const QString &fileName) +{ + QByteArray data; + QFile f(fileName); + if (!(f.fileEngine()->fileFlags(QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::LocalDiskFlag)) { + if (!f.open(QIODevice::ReadOnly)) + return -1; + data = f.readAll(); + } + QMutexLocker locker(fontDatabaseMutex()); + return privateDb()->addAppFont(data, fileName); +} + +/*! + \since 4.2 + + Loads the font from binary data specified by \a fontData and makes it available to + the application. An ID is returned that can be used to remove the font again + with removeApplicationFont() or to retrieve the list of family names contained + in the font. + + The function returns -1 if the font could not be loaded. + + Currently only TrueType fonts and TrueType font collections are supported. + + \bold{Note:} Adding application fonts on Unix/X11 platforms without fontconfig is + currently not supported. + + \note On Symbian, the font family names get truncated to a length of 20 characters. + + \sa addApplicationFont(), applicationFontFamilies(), removeApplicationFont() +*/ +int QFontDatabase::addApplicationFontFromData(const QByteArray &fontData) +{ + QMutexLocker locker(fontDatabaseMutex()); + return privateDb()->addAppFont(fontData, QString() /* fileName */); +} + +/*! + \since 4.2 + + Returns a list of font families for the given application font identified by + \a id. + + \sa addApplicationFont(), addApplicationFontFromData() +*/ +QStringList QFontDatabase::applicationFontFamilies(int id) +{ + QMutexLocker locker(fontDatabaseMutex()); + return privateDb()->applicationFonts.value(id).families; +} + +/*! + \fn bool QFontDatabase::removeApplicationFont(int id) + \since 4.2 + + Removes the previously loaded application font identified by \a + id. Returns true if unloading of the font succeeded; otherwise + returns false. + + \sa removeAllApplicationFonts(), addApplicationFont(), + addApplicationFontFromData() +*/ + +/*! + \fn bool QFontDatabase::removeAllApplicationFonts() + \since 4.2 + + Removes all application-local fonts previously added using addApplicationFont() + and addApplicationFontFromData(). + + Returns true if unloading of the fonts succeeded; otherwise + returns false. + + \sa removeApplicationFont(), addApplicationFont(), addApplicationFontFromData() +*/ + +/*! + \fn bool QFontDatabase::supportsThreadedFontRendering() + \since 4.4 + + Returns true if font rendering is supported outside the GUI + thread, false otherwise. In other words, a return value of false + means that all QPainter::drawText() calls outside the GUI thread + will not produce readable output. + + \sa {Thread-Support in Qt Modules#Painting In Threads}{Painting In Threads} +*/ + + +QT_END_NAMESPACE + diff --git a/src/gui/text/qfontdatabase.h b/src/gui/text/qfontdatabase.h new file mode 100644 index 0000000000..ae1130eadc --- /dev/null +++ b/src/gui/text/qfontdatabase.h @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTDATABASE_H +#define QFONTDATABASE_H + +#include +#include +#include +#ifdef QT3_SUPPORT +#include +#include +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QStringList; +template class QList; +struct QFontDef; +class QFontEngine; + +class QFontDatabasePrivate; + +class Q_GUI_EXPORT QFontDatabase +{ + Q_GADGET + Q_ENUMS(WritingSystem) +public: + // do not re-order or delete entries from this enum without updating the + // QPF2 format and makeqpf!! + enum WritingSystem { + Any, + + Latin, + Greek, + Cyrillic, + Armenian, + Hebrew, + Arabic, + Syriac, + Thaana, + Devanagari, + Bengali, + Gurmukhi, + Gujarati, + Oriya, + Tamil, + Telugu, + Kannada, + Malayalam, + Sinhala, + Thai, + Lao, + Tibetan, + Myanmar, + Georgian, + Khmer, + SimplifiedChinese, + TraditionalChinese, + Japanese, + Korean, + Vietnamese, + + Symbol, + Other = Symbol, + + Ogham, + Runic, + Nko, + + WritingSystemsCount + }; + + static QList standardSizes(); + + QFontDatabase(); + + QList writingSystems() const; + QList writingSystems(const QString &family) const; + + QStringList families(WritingSystem writingSystem = Any) const; + QStringList styles(const QString &family) const; + QList pointSizes(const QString &family, const QString &style = QString()); + QList smoothSizes(const QString &family, const QString &style); + QString styleString(const QFont &font); + QString styleString(const QFontInfo &fontInfo); + + QFont font(const QString &family, const QString &style, int pointSize) const; + + bool isBitmapScalable(const QString &family, const QString &style = QString()) const; + bool isSmoothlyScalable(const QString &family, const QString &style = QString()) const; + bool isScalable(const QString &family, const QString &style = QString()) const; + bool isFixedPitch(const QString &family, const QString &style = QString()) const; + + bool italic(const QString &family, const QString &style) const; + bool bold(const QString &family, const QString &style) const; + int weight(const QString &family, const QString &style) const; + + static QString writingSystemName(WritingSystem writingSystem); + static QString writingSystemSample(WritingSystem writingSystem); + + static int addApplicationFont(const QString &fileName); + static int addApplicationFontFromData(const QByteArray &fontData); + static QStringList applicationFontFamilies(int id); + static bool removeApplicationFont(int id); + static bool removeAllApplicationFonts(); + + static bool supportsThreadedFontRendering(); + +private: + static void createDatabase(); + static void parseFontName(const QString &name, QString &foundry, QString &family); + static QString resolveFontFamilyAlias(const QString &family); +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) + static QFontEngine *findFont(int script, const QFontPrivate *fp, const QFontDef &request); +#endif + static void load(const QFontPrivate *d, int script); +#ifdef Q_WS_X11 + static QFontEngine *loadXlfd(int screen, int script, const QFontDef &request, int force_encoding_id = -1); +#endif + + friend struct QFontDef; + friend class QFontPrivate; + friend class QFontDialog; + friend class QFontDialogPrivate; + friend class QFontEngineMultiXLFD; + friend class QFontEngineMultiQWS; + friend class QFontEngineMultiS60; + friend class QFontEngineMultiQPA; + + QFontDatabasePrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFONTDATABASE_H diff --git a/src/gui/text/qfontdatabase_mac.cpp b/src/gui/text/qfontdatabase_mac.cpp new file mode 100644 index 0000000000..5ba236b5f7 --- /dev/null +++ b/src/gui/text/qfontdatabase_mac.cpp @@ -0,0 +1,466 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "qfontengine_p.h" +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +int qt_mac_pixelsize(const QFontDef &def, int dpi); //qfont_mac.cpp +int qt_mac_pointsize(const QFontDef &def, int dpi); //qfont_mac.cpp + +#ifndef QT_MAC_USE_COCOA +static void initWritingSystems(QtFontFamily *family, ATSFontRef atsFont) +{ + ByteCount length = 0; + if (ATSFontGetTable(atsFont, MAKE_TAG('O', 'S', '/', '2'), 0, 0, 0, &length) != noErr) + return; + QVarLengthArray os2Table(length); + if (length < 86 + || ATSFontGetTable(atsFont, MAKE_TAG('O', 'S', '/', '2'), 0, length, os2Table.data(), &length) != noErr) + return; + + // See also qfontdatabase_win.cpp, offsets taken from OS/2 table in the TrueType spec + quint32 unicodeRange[4] = { + qFromBigEndian(os2Table.data() + 42), + qFromBigEndian(os2Table.data() + 46), + qFromBigEndian(os2Table.data() + 50), + qFromBigEndian(os2Table.data() + 54) + }; + quint32 codePageRange[2] = { qFromBigEndian(os2Table.data() + 78), qFromBigEndian(os2Table.data() + 82) }; + QList systems = qt_determine_writing_systems_from_truetype_bits(unicodeRange, codePageRange); +#if 0 + QCFString name; + ATSFontGetName(atsFont, kATSOptionFlagsDefault, &name); + qDebug() << systems.count() << "writing systems for" << QString(name); +qDebug() << "first char" << hex << unicodeRange[0]; + for (int i = 0; i < systems.count(); ++i) + qDebug() << QFontDatabase::writingSystemName(systems.at(i)); +#endif + for (int i = 0; i < systems.count(); ++i) + family->writingSystems[systems.at(i)] = QtFontFamily::Supported; +} +#endif + +static void initializeDb() +{ + QFontDatabasePrivate *db = privateDb(); + if(!db || db->count) + return; + +#if defined(QT_MAC_USE_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 +if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + QCFType collection = CTFontCollectionCreateFromAvailableFonts(0); + if(!collection) + return; + QCFType fonts = CTFontCollectionCreateMatchingFontDescriptors(collection); + if(!fonts) + return; + QString foundry_name = "CoreText"; + const int numFonts = CFArrayGetCount(fonts); + for(int i = 0; i < numFonts; ++i) { + CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fonts, i); + + QCFString family_name = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute); + QtFontFamily *family = db->family(family_name, true); + for(int ws = 1; ws < QFontDatabase::WritingSystemsCount; ++ws) + family->writingSystems[ws] = QtFontFamily::Supported; + QtFontFoundry *foundry = family->foundry(foundry_name, true); + + QtFontStyle::Key styleKey; + if(QCFType styles = (CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute)) { + if(CFNumberRef weight = (CFNumberRef)CFDictionaryGetValue(styles, kCTFontWeightTrait)) { + Q_ASSERT(CFNumberIsFloatType(weight)); + double d; + if(CFNumberGetValue(weight, kCFNumberDoubleType, &d)) { + //qDebug() << "BOLD" << (QString)family_name << d; + styleKey.weight = (d > 0.0) ? QFont::Bold : QFont::Normal; + } + } + if(CFNumberRef italic = (CFNumberRef)CFDictionaryGetValue(styles, kCTFontSlantTrait)) { + Q_ASSERT(CFNumberIsFloatType(italic)); + double d; + if(CFNumberGetValue(italic, kCFNumberDoubleType, &d)) { + //qDebug() << "ITALIC" << (QString)family_name << d; + if (d > 0.0) + styleKey.style = QFont::StyleItalic; + } + } + } + + QtFontStyle *style = foundry->style(styleKey, true); + style->smoothScalable = true; + if(QCFType size = (CFNumberRef)CTFontDescriptorCopyAttribute(font, kCTFontSizeAttribute)) { + //qDebug() << "WHEE"; + int pixel_size=0; + if(CFNumberIsFloatType(size)) { + double d; + CFNumberGetValue(size, kCFNumberDoubleType, &d); + pixel_size = d; + } else { + CFNumberGetValue(size, kCFNumberIntType, &pixel_size); + } + //qDebug() << "SIZE" << (QString)family_name << pixel_size; + if(pixel_size) + style->pixelSize(pixel_size, true); + } else { + //qDebug() << "WTF?"; + } + } +} else +#endif + { +#ifndef QT_MAC_USE_COCOA + FMFontIterator it; + if (!FMCreateFontIterator(0, 0, kFMUseGlobalScopeOption, &it)) { + while (true) { + FMFont fmFont; + if (FMGetNextFont(&it, &fmFont) != noErr) + break; + + FMFontFamily fmFamily; + FMFontStyle fmStyle; + QString familyName; + + QtFontStyle::Key styleKey; + + ATSFontRef atsFont = FMGetATSFontRefFromFont(fmFont); + + if (!FMGetFontFamilyInstanceFromFont(fmFont, &fmFamily, &fmStyle)) { + { //sanity check the font, and see if we can use it at all! --Sam + ATSUFontID fontID; + if(ATSUFONDtoFontID(fmFamily, 0, &fontID) != noErr) + continue; + } + + if (fmStyle & ::italic) + styleKey.style = QFont::StyleItalic; + if (fmStyle & ::bold) + styleKey.weight = QFont::Bold; + + ATSFontFamilyRef familyRef = FMGetATSFontFamilyRefFromFontFamily(fmFamily); + QCFString cfFamilyName;; + ATSFontFamilyGetName(familyRef, kATSOptionFlagsDefault, &cfFamilyName); + familyName = cfFamilyName; + } else { + QCFString cfFontName; + ATSFontGetName(atsFont, kATSOptionFlagsDefault, &cfFontName); + familyName = cfFontName; + quint16 macStyle = 0; + { + uchar data[4]; + ByteCount len = 4; + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 44, 4, &data, &len) == noErr) + macStyle = qFromBigEndian(data); + } + if (macStyle & 1) + styleKey.weight = QFont::Bold; + if (macStyle & 2) + styleKey.style = QFont::StyleItalic; + } + + QtFontFamily *family = db->family(familyName, true); + QtFontFoundry *foundry = family->foundry(QString(), true); + QtFontStyle *style = foundry->style(styleKey, true); + style->pixelSize(0, true); + style->smoothScalable = true; + + initWritingSystems(family, atsFont); + } + FMDisposeFontIterator(&it); + } +#endif + } + +} + +static inline void load(const QString & = QString(), int = -1) +{ + initializeDb(); +} + +static const char *styleHint(const QFontDef &request) +{ + const char *stylehint = 0; + switch (request.styleHint) { + case QFont::SansSerif: + stylehint = "Arial"; + break; + case QFont::Serif: + stylehint = "Times New Roman"; + break; + case QFont::TypeWriter: + stylehint = "Courier New"; + break; + default: + if (request.fixedPitch) + stylehint = "Courier New"; + break; + } + return stylehint; +} + +static inline float weightToFloat(unsigned int weight) +{ + return (weight - 50) / 100.0; +} + +void QFontDatabase::load(const QFontPrivate *d, int script) +{ + // sanity checks + if(!qApp) + qWarning("QFont: Must construct a QApplication before a QFont"); + + Q_ASSERT(script >= 0 && script < QUnicodeTables::ScriptCount); + Q_UNUSED(script); + + QFontDef req = d->request; + req.pixelSize = qt_mac_pixelsize(req, d->dpi); + + // set the point size to 0 to get better caching + req.pointSize = 0; + QFontCache::Key key = QFontCache::Key(req, QUnicodeTables::Common, d->screen); + + if(!(d->engineData = QFontCache::instance()->findEngineData(key))) { + d->engineData = new QFontEngineData; + QFontCache::instance()->insertEngineData(key, d->engineData); + } else { + d->engineData->ref.ref(); + } + if(d->engineData->engine) // already loaded + return; + + // set it to the actual pointsize, so QFontInfo will do the right thing + req.pointSize = qRound(qt_mac_pointsize(d->request, d->dpi)); + + QFontEngine *e = QFontCache::instance()->findEngine(key); + if(!e && qt_enable_test_font && req.family == QLatin1String("__Qt__Box__Engine__")) { + e = new QTestFontEngine(req.pixelSize); + e->fontDef = req; + } + + if(e) { + e->ref.ref(); + d->engineData->engine = e; + return; // the font info and fontdef should already be filled + } + + //find the font + QStringList family_list = familyList(req); + + const char *stylehint = styleHint(req); + if (stylehint) + family_list << QLatin1String(stylehint); + + // add QFont::defaultFamily() to the list, for compatibility with + // previous versions + family_list << QApplication::font().defaultFamily(); + +#if defined(QT_MAC_USE_COCOA) + QCFString fontName = NULL, familyName = NULL; +#else + ATSFontFamilyRef familyRef = 0; + ATSFontRef fontRef = 0; +#endif + + QMutexLocker locker(fontDatabaseMutex()); + QFontDatabasePrivate *db = privateDb(); + if (!db->count) + initializeDb(); + for(int i = 0; i < family_list.size(); ++i) { + for (int k = 0; k < db->count; ++k) { + if (db->families[k]->name.compare(family_list.at(i), Qt::CaseInsensitive) == 0) { + QByteArray family_name = db->families[k]->name.toUtf8(); +#if defined(QT_MAC_USE_COCOA) + QCFType ctFont = CTFontCreateWithName(QCFString(db->families[k]->name), 12, NULL); + if (ctFont) { + fontName = CTFontCopyFullName(ctFont); + familyName = CTFontCopyFamilyName(ctFont); + goto FamilyFound; + } +#else + familyRef = ATSFontFamilyFindFromName(QCFString(db->families[k]->name), kATSOptionFlagsDefault); + if (familyRef) { + fontRef = ATSFontFindFromName(QCFString(db->families[k]->name), kATSOptionFlagsDefault); + goto FamilyFound; + } +#endif + } + } + } +FamilyFound: + //fill in the engine's font definition + QFontDef fontDef = d->request; //copy.. + if(fontDef.pointSize < 0) + fontDef.pointSize = qt_mac_pointsize(fontDef, d->dpi); + else + fontDef.pixelSize = qt_mac_pixelsize(fontDef, d->dpi); + +#ifdef QT_MAC_USE_COCOA + fontDef.family = familyName; + QFontEngine *engine = new QCoreTextFontEngineMulti(fontName, fontDef, d->kerning); +#else + QCFString actualName; + if (ATSFontFamilyGetName(familyRef, kATSOptionFlagsDefault, &actualName) == noErr) + fontDef.family = actualName; + QFontEngine *engine = new QFontEngineMacMulti(familyRef, fontRef, fontDef, d->kerning); +#endif + d->engineData->engine = engine; + engine->ref.ref(); //a ref for the engineData->engine + QFontCache::instance()->insertEngine(key, engine); +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt) +{ + ATSFontContainerRef handle; + OSStatus e = noErr; + + if(fnt->data.isEmpty()) { +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + extern OSErr qt_mac_create_fsref(const QString &, FSRef *); // qglobal.cpp + FSRef ref; + if(qt_mac_create_fsref(fnt->fileName, &ref) != noErr) + return; + + ATSFontActivateFromFileReference(&ref, kATSFontContextLocal, kATSFontFormatUnspecified, 0, kATSOptionFlagsDefault, &handle); + } else +#endif + { +#ifndef Q_WS_MAC64 + extern Q_CORE_EXPORT OSErr qt_mac_create_fsspec(const QString &, FSSpec *); // global.cpp + FSSpec spec; + if(qt_mac_create_fsspec(fnt->fileName, &spec) != noErr) + return; + + e = ATSFontActivateFromFileSpecification(&spec, kATSFontContextLocal, kATSFontFormatUnspecified, + 0, kATSOptionFlagsDefault, &handle); +#endif + } + } else { + e = ATSFontActivateFromMemory((void *)fnt->data.constData(), fnt->data.size(), kATSFontContextLocal, + kATSFontFormatUnspecified, 0, kATSOptionFlagsDefault, &handle); + + fnt->data = QByteArray(); + } + + if(e != noErr) + return; + + ItemCount fontCount = 0; + e = ATSFontFindFromContainer(handle, kATSOptionFlagsDefault, 0, 0, &fontCount); + if(e != noErr) + return; + + QVarLengthArray containedFonts(fontCount); + e = ATSFontFindFromContainer(handle, kATSOptionFlagsDefault, fontCount, containedFonts.data(), &fontCount); + if(e != noErr) + return; + + fnt->families.clear(); +#if defined(QT_MAC_USE_COCOA) + // Make sure that the family name set on the font matches what + // kCTFontFamilyNameAttribute returns in initializeDb(). + // So far the best solution seems find the installed font + // using CoreText and get the family name from it. + // (ATSFontFamilyGetName appears to be the correct API, but also + // returns the font display name.) + for(int i = 0; i < containedFonts.size(); ++i) { + QCFString fontPostScriptName; + ATSFontGetPostScriptName(containedFonts[i], kATSOptionFlagsDefault, &fontPostScriptName); + QCFType font = CTFontDescriptorCreateWithNameAndSize(fontPostScriptName, 14); + QCFString familyName = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute); + fnt->families.append(familyName); + } +#else + for(int i = 0; i < containedFonts.size(); ++i) { + QCFString family; + ATSFontGetName(containedFonts[i], kATSOptionFlagsDefault, &family); + fnt->families.append(family); + } +#endif + + fnt->handle = handle; +} + +bool QFontDatabase::removeApplicationFont(int handle) +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if(handle < 0 || handle >= db->applicationFonts.count()) + return false; + + OSStatus e = ATSFontDeactivate(db->applicationFonts.at(handle).handle, + /*iRefCon=*/0, kATSOptionFlagsDefault); + if(e != noErr) + return false; + + db->applicationFonts[handle] = QFontDatabasePrivate::ApplicationFont(); + + db->invalidate(); + return true; +} + +bool QFontDatabase::removeAllApplicationFonts() +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + for(int i = 0; i < db->applicationFonts.count(); ++i) { + if(!removeApplicationFont(i)) + return false; + } + return true; +} + +bool QFontDatabase::supportsThreadedFontRendering() +{ + return true; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontdatabase_qpa.cpp b/src/gui/text/qfontdatabase_qpa.cpp new file mode 100644 index 0000000000..7bcce56ac8 --- /dev/null +++ b/src/gui/text/qfontdatabase_qpa.cpp @@ -0,0 +1,394 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlibraryinfo.h" +#include + +#include "qfontengine_qpa_p.h" +#include "qplatformdefs.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +Q_GUI_EXPORT void qt_registerFont(const QString &familyName, const QString &foundryname, int weight, + QFont::Style style, int stretch, bool antialiased, bool scalable, int pixelSize, + const QSupportedWritingSystems &writingSystems, void *handle) +{ + QFontDatabasePrivate *d = privateDb(); + // qDebug() << "Adding font" << familyname << weight << italic << pixelSize << file << fileIndex << antialiased; + QtFontStyle::Key styleKey; + styleKey.style = style; + styleKey.weight = weight; + styleKey.stretch = stretch; + QtFontFamily *f = d->family(familyName, true); + + for (int i = 0; i < QFontDatabase::WritingSystemsCount; ++i) { + if (writingSystems.supported(QFontDatabase::WritingSystem(i))) { + f->writingSystems[i] = QtFontFamily::Supported; + } else { + f->writingSystems[i] = QtFontFamily::Unsupported; + } + } + + QtFontFoundry *foundry = f->foundry(foundryname, true); + QtFontStyle *fontStyle = foundry->style(styleKey, true); + fontStyle->smoothScalable = scalable; + fontStyle->antialiased = antialiased; + QtFontSize *size = fontStyle->pixelSize(pixelSize?pixelSize:SMOOTH_SCALABLE, true); + size->handle = handle; +} + +static QStringList fallbackFamilies(const QString &family, const QFont::Style &style, const QFont::StyleHint &styleHint, const QUnicodeTables::Script &script) +{ + QStringList retList = QApplicationPrivate::platformIntegration()->fontDatabase()->fallbacksForFamily(family,style,styleHint,script); + QFontDatabasePrivate *db = privateDb(); + + QStringList::iterator i; + for (i = retList.begin(); i != retList.end(); ++i) { + bool contains = false; + for (int j = 0; j < db->count; j++) { + QtFontFamily *qtFamily = db->families[j]; + if (!(i->compare(qtFamily->name,Qt::CaseInsensitive))) { + contains = true; + break; + } + } + if (!contains) { + i = retList.erase(i); + i--; + } + } + return retList; +} + +static void initializeDb() +{ + static int initialized = false; + + if (!initialized) { + //init by asking for the platformfontdb for the first time :) + QApplicationPrivate::platformIntegration()->fontDatabase()->populateFontDatabase(); + initialized = true; + } +} + +#ifndef QT_NO_SETTINGS +// called from qapplication_qws.cpp +void qt_applyFontDatabaseSettings(const QSettings &settings) +{ + initializeDb(); + QFontDatabasePrivate *db = privateDb(); + for (int i = 0; i < db->count; ++i) { + QtFontFamily *family = db->families[i]; + if (settings.contains(family->name)) + family->fallbackFamilies = settings.value(family->name).toStringList(); + } + + if (settings.contains(QLatin1String("Global Fallbacks"))) + db->fallbackFamilies = settings.value(QLatin1String("Global Fallbacks")).toStringList(); +} +#endif // QT_NO_SETTINGS + +static inline void load(const QString & = QString(), int = -1) +{ + initializeDb(); +} + +static +QFontEngine *loadSingleEngine(int script, + const QFontDef &request, + QtFontFoundry *foundry, + QtFontStyle *style, QtFontSize *size) +{ + Q_UNUSED(foundry); + + Q_ASSERT(size); + int pixelSize = size->pixelSize; + if (!pixelSize || (style->smoothScalable && pixelSize == SMOOTH_SCALABLE)) + pixelSize = request.pixelSize; + + QFontDef def = request; + def.pixelSize = pixelSize; + + QFontCache::Key key(def,script); + QFontEngine *engine = QFontCache::instance()->findEngine(key); + if (!engine) { + QPlatformFontDatabase *pfdb = QApplicationPrivate::platformIntegration()->fontDatabase(); + engine = pfdb->fontEngine(def,QUnicodeTables::Script(script),size->handle); + if (engine) { + QFontCache::Key key(def,script); + QFontCache::instance()->instance()->insertEngine(key,engine); + } + } + return engine; +} + +static +QFontEngine *loadEngine(int script, const QFontDef &request, + QtFontFamily *family, QtFontFoundry *foundry, + QtFontStyle *style, QtFontSize *size) +{ + + QFontEngine *engine = loadSingleEngine(script, request, foundry, style, size); + //make sure that the db has all fallback families + if (engine + && !(request.styleStrategy & QFont::NoFontMerging) && !engine->symbol ) { + + if (family && !family->askedForFallback) { + QFont::Style fontStyle = QFont::Style(style->key.style); + QFont::StyleHint styleHint = QFont::StyleHint(request.styleHint); + if (styleHint == QFont::AnyStyle && request.fixedPitch) + styleHint = QFont::TypeWriter; + family->fallbackFamilies = fallbackFamilies(family->name,fontStyle,styleHint,QUnicodeTables::Script(script)); + + family->askedForFallback = true; + } + + QStringList fallbacks = privateDb()->fallbackFamilies; + if (family && !family->fallbackFamilies.isEmpty()) + fallbacks = family->fallbackFamilies; + + engine = new QFontEngineMultiQPA(engine, script, fallbacks); + } + + return engine; +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt) +{ + QFontDatabasePrivate *db = privateDb(); + + fnt->families = QApplicationPrivate::platformIntegration()->fontDatabase()->addApplicationFont(fnt->data,fnt->fileName); + + db->reregisterAppFonts = true; +} + +bool QFontDatabase::removeApplicationFont(int handle) +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (handle < 0 || handle >= db->applicationFonts.count()) + return false; + + db->applicationFonts[handle] = QFontDatabasePrivate::ApplicationFont(); + + db->reregisterAppFonts = true; + db->invalidate(); + return true; +} + +bool QFontDatabase::removeAllApplicationFonts() +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (db->applicationFonts.isEmpty()) + return false; + + db->applicationFonts.clear(); + db->invalidate(); + return true; +} + +bool QFontDatabase::supportsThreadedFontRendering() +{ + return true; +} + +/*! + \internal +*/ +QFontEngine * +QFontDatabase::findFont(int script, const QFontPrivate *fp, + const QFontDef &request) +{ + QMutexLocker locker(fontDatabaseMutex()); + + const int force_encoding_id = -1; + + if (!privateDb()->count) + initializeDb(); + + QFontEngine *engine; + QFontCache::Key key(request, script); + engine = QFontCache::instance()->findEngine(key); + if (engine) { + qDebug() << "Cache hit level 1"; + return engine; + } + + QString family_name, foundry_name; + + parseFontName(request.family, foundry_name, family_name); + + if (qt_enable_test_font && request.family == QLatin1String("__Qt__Box__Engine__")) { + engine =new QTestFontEngine(request.pixelSize); + engine->fontDef = request; + } + + QtFontDesc desc; + match(script, request, family_name, foundry_name, force_encoding_id, &desc); + if (desc.family != 0 && desc.foundry != 0 && desc.style != 0) { + engine = loadEngine(script, request, desc.family, desc.foundry, desc.style, desc.size); + } else { + FM_DEBUG(" NO MATCH FOUND\n"); + } + + if (engine) { + initFontDef(desc, request, &engine->fontDef); + + if (fp) { + QFontDef def = request; + if (def.family.isEmpty()) { + def.family = fp->request.family; + def.family = def.family.left(def.family.indexOf(QLatin1Char(','))); + } + } + } + + if (!engine) { + if (!request.family.isEmpty()) { + QStringList fallbacks = fallbackFamilies(request.family,QFont::Style(request.style),QFont::StyleHint(request.styleHint),QUnicodeTables::Script(script)); + for (int i = 0; i < fallbacks.size(); i++) { + QFontDef def = request; + def.family = fallbacks.at(i); + QFontCache::Key key(def,script); + engine = QFontCache::instance()->findEngine(key); + if (!engine) { + QtFontDesc desc; + match(script, def, def.family, QLatin1String(""), 0, &desc); + if (desc.family == 0 && desc.foundry == 0 && desc.style == 0) { + continue; + } + engine = loadEngine(script, def, desc.family, desc.foundry, desc.style, desc.size); + if (engine) { + initFontDef(desc, def, &engine->fontDef); + break; + } + } + } + } + + if (!engine) + engine = new QFontEngineBox(request.pixelSize); + + FM_DEBUG("returning box engine"); + } + + if (fp && fp->dpi > 0) { + engine->fontDef.pointSize = qreal(double((engine->fontDef.pixelSize * 72) / fp->dpi)); + } else { + engine->fontDef.pointSize = request.pointSize; + } + + return engine; +} + +void QFontDatabase::load(const QFontPrivate *d, int script) +{ + QFontDef req = d->request; + + if (req.pixelSize == -1) { + req.pixelSize = floor(((req.pointSize * d->dpi) / 72) * 100 + 0.5) / 100; + req.pixelSize = qRound(req.pixelSize); + } + if (req.pointSize < 0) + req.pointSize = req.pixelSize*72.0/d->dpi; + if (req.weight == 0) + req.weight = QFont::Normal; + if (req.stretch == 0) + req.stretch = 100; + + QFontCache::Key key(req, script); + + if (!d->engineData) + getEngineData(d, key); + + // the cached engineData could have already loaded the engine we want + if (d->engineData->engines[script]) + return; + + QFontEngine *fe = QFontCache::instance()->findEngine(key); + + // list of families to try + QStringList family_list; + + if (!req.family.isEmpty()) { + family_list = familyList(req); + + // add the default family + QString defaultFamily = QApplication::font().family(); + if (! family_list.contains(defaultFamily)) + family_list << defaultFamily; + + } + + // null family means find the first font matching the specified script + family_list << QString(); + + QStringList::ConstIterator it = family_list.constBegin(), end = family_list.constEnd(); + for (; !fe && it != end; ++it) { + req.family = *it; + + fe = QFontDatabase::findFont(script, d, req); + if (fe && (fe->type()==QFontEngine::Box) && !req.family.isEmpty()) + fe = 0; + } + + if (fe->symbol || (d->request.styleStrategy & QFont::NoFontMerging)) { + for (int i = 0; i < QUnicodeTables::ScriptCount; ++i) { + if (!d->engineData->engines[i]) { + d->engineData->engines[i] = fe; + fe->ref.ref(); + } + } + } else { + d->engineData->engines[script] = fe; + fe->ref.ref(); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontdatabase_qws.cpp b/src/gui/text/qfontdatabase_qws.cpp new file mode 100644 index 0000000000..d076de05d3 --- /dev/null +++ b/src/gui/text/qfontdatabase_qws.cpp @@ -0,0 +1,975 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdir.h" +#if defined(Q_WS_QWS) +#include "qscreen_qws.h" //so we can check for rotation +#include "qwindowsystem_qws.h" +#endif +#include "qlibraryinfo.h" +#include "qabstractfileengine.h" +#include +#if !defined(QT_NO_FREETYPE) +#include "qfontengine_ft_p.h" + +#include +#include FT_FREETYPE_H + +#endif +#include "qfontengine_qpf_p.h" +#include "private/qfactoryloader_p.h" +#include "private/qcore_unix_p.h" // overrides QT_OPEN +#include "qabstractfontengine_qws.h" +#include "qabstractfontengine_p.h" +#include +#include "qplatformdefs.h" + +// for mmap +#include +#include +#include +#include +#include +#include +#include + +#ifdef QT_FONTS_ARE_RESOURCES +#include +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LIBRARY +Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, + (QFontEngineFactoryInterface_iid, QLatin1String("/fontengines"), Qt::CaseInsensitive)) +#endif + +const quint8 DatabaseVersion = 4; + +// QFontDatabasePrivate::addFont() went into qfontdatabase.cpp + +#ifndef QT_NO_QWS_QPF2 +void QFontDatabasePrivate::addQPF2File(const QByteArray &file) +{ +#ifndef QT_FONTS_ARE_RESOURCES + struct stat st; + if (stat(file.constData(), &st)) + return; + int f = QT_OPEN(file, O_RDONLY, 0); + if (f < 0) + return; + const uchar *data = (const uchar *)mmap(0, st.st_size, PROT_READ, MAP_SHARED, f, 0); + const int dataSize = st.st_size; +#else + QResource res(QLatin1String(file.constData())); + const uchar *data = res.data(); + const int dataSize = res.size(); + //qDebug() << "addQPF2File" << file << data; +#endif + if (data && data != (const uchar *)MAP_FAILED) { + if (QFontEngineQPF::verifyHeader(data, dataSize)) { + QString fontName = QFontEngineQPF::extractHeaderField(data, QFontEngineQPF::Tag_FontName).toString(); + int pixelSize = QFontEngineQPF::extractHeaderField(data, QFontEngineQPF::Tag_PixelSize).toInt(); + QVariant weight = QFontEngineQPF::extractHeaderField(data, QFontEngineQPF::Tag_Weight); + QVariant style = QFontEngineQPF::extractHeaderField(data, QFontEngineQPF::Tag_Style); + QByteArray writingSystemBits = QFontEngineQPF::extractHeaderField(data, QFontEngineQPF::Tag_WritingSystems).toByteArray(); + + if (!fontName.isEmpty() && pixelSize) { + int fontWeight = 50; + if (weight.type() == QVariant::Int || weight.type() == QVariant::UInt) + fontWeight = weight.toInt(); + + bool italic = static_cast(style.toInt()) & QFont::StyleItalic; + + QList writingSystems; + for (int i = 0; i < writingSystemBits.count(); ++i) { + uchar currentByte = writingSystemBits.at(i); + for (int j = 0; j < 8; ++j) { + if (currentByte & 1) + writingSystems << QFontDatabase::WritingSystem(i * 8 + j); + currentByte >>= 1; + } + } + + addFont(fontName, /*foundry*/ "prerendered", fontWeight, italic, + pixelSize, file, /*fileIndex*/ 0, + /*antialiased*/ true, writingSystems); + } + } else { + qDebug() << "header verification of QPF2 font" << file << "failed. maybe it is corrupt?"; + } +#ifndef QT_FONTS_ARE_RESOURCES + munmap((void *)data, st.st_size); +#endif + } +#ifndef QT_FONTS_ARE_RESOURCES + QT_CLOSE(f); +#endif +} +#endif // QT_NO_QWS_QPF2 + +// QFontDatabasePrivate::addTTFile() went into qfontdatabase.cpp + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt); + +extern QString qws_fontCacheDir(); + +#ifndef QT_FONTS_ARE_RESOURCES +bool QFontDatabasePrivate::loadFromCache(const QString &fontPath) +{ +#ifdef Q_WS_QWS + const bool weAreTheServer = QWSServer::instance(); +#else + const bool weAreTheServer = true; // assume single-process +#endif + + QString fontDirFile = fontPath + QLatin1String("/fontdir"); + + QFile binaryDb(qws_fontCacheDir() + QLatin1String("/fontdb")); + + if (weAreTheServer) { + QDateTime dbTimeStamp = QFileInfo(binaryDb.fileName()).lastModified(); + + QDateTime fontPathTimeStamp = QFileInfo(fontPath).lastModified(); + if (dbTimeStamp < fontPathTimeStamp) + return false; // let the caller create the cache + + if (QFile::exists(fontDirFile)) { + QDateTime fontDirTimeStamp = QFileInfo(fontDirFile).lastModified(); + if (dbTimeStamp < fontDirTimeStamp) + return false; + } + } + + if (!binaryDb.open(QIODevice::ReadOnly)) { + if (weAreTheServer) + return false; // let the caller create the cache + qFatal("QFontDatabase::loadFromCache: Could not open font database cache!"); + } + + QDataStream stream(&binaryDb); + quint8 version = 0; + quint8 dataStreamVersion = 0; + stream >> version >> dataStreamVersion; + if (version != DatabaseVersion || dataStreamVersion != stream.version()) { + if (weAreTheServer) + return false; // let the caller create the cache + qFatal("QFontDatabase::loadFromCache: Wrong version of the font database cache detected. Found %d/%d expected %d/%d", + version, dataStreamVersion, DatabaseVersion, stream.version()); + } + + QString originalFontPath; + stream >> originalFontPath; + if (originalFontPath != fontPath) { + if (weAreTheServer) + return false; // let the caller create the cache + qFatal("QFontDatabase::loadFromCache: Font path doesn't match. Found %s in database, expected %s", qPrintable(originalFontPath), qPrintable(fontPath)); + } + + QString familyname; + stream >> familyname; + //qDebug() << "populating database from" << binaryDb.fileName(); + while (!familyname.isEmpty() && !stream.atEnd()) { + QString foundryname; + int weight; + quint8 italic; + int pixelSize; + QByteArray file; + int fileIndex; + quint8 antialiased; + quint8 writingSystemCount; + + QList writingSystems; + + stream >> foundryname >> weight >> italic >> pixelSize + >> file >> fileIndex >> antialiased >> writingSystemCount; + + for (quint8 i = 0; i < writingSystemCount; ++i) { + quint8 ws; + stream >> ws; + writingSystems.append(QFontDatabase::WritingSystem(ws)); + } + + addFont(familyname, foundryname.toLatin1().constData(), weight, italic, pixelSize, file, fileIndex, antialiased, + writingSystems); + + stream >> familyname; + } + + stream >> fallbackFamilies; + //qDebug() << "fallback families from cache:" << fallbackFamilies; + return true; +} +#endif // QT_FONTS_ARE_RESOURCES + +/*! + \internal +*/ + +static QString qwsFontPath() +{ + QString fontpath = QString::fromLocal8Bit(qgetenv("QT_QWS_FONTDIR")); + if (fontpath.isEmpty()) { +#ifdef QT_FONTS_ARE_RESOURCES + fontpath = QLatin1String(":/qt/fonts"); +#else +#ifndef QT_NO_SETTINGS + fontpath = QLibraryInfo::location(QLibraryInfo::LibrariesPath); + fontpath += QLatin1String("/fonts"); +#else + fontpath = QLatin1String("/lib/fonts"); +#endif +#endif //QT_FONTS_ARE_RESOURCES + } + + return fontpath; +} + +#if defined(QFONTDATABASE_DEBUG) && defined(QT_FONTS_ARE_RESOURCES) +class FriendlyResource : public QResource +{ +public: + bool isDir () const { return QResource::isDir(); } + bool isFile () const { return QResource::isFile(); } + QStringList children () const { return QResource::children(); } +}; +#endif +/*! + \internal +*/ +static void initializeDb() +{ + QFontDatabasePrivate *db = privateDb(); + if (!db || db->count) + return; + + QString fontpath = qwsFontPath(); +#ifndef QT_FONTS_ARE_RESOURCES + QString fontDirFile = fontpath + QLatin1String("/fontdir"); + + if(!QFile::exists(fontpath)) { + qFatal("QFontDatabase: Cannot find font directory %s - is Qt installed correctly?", + fontpath.toLocal8Bit().constData()); + } + + const bool loaded = db->loadFromCache(fontpath); + + if (db->reregisterAppFonts) { + db->reregisterAppFonts = false; + for (int i = 0; i < db->applicationFonts.count(); ++i) + if (!db->applicationFonts.at(i).families.isEmpty()) { + registerFont(&db->applicationFonts[i]); + } + } + + if (loaded) + return; + + QString dbFileName = qws_fontCacheDir() + QLatin1String("/fontdb"); + + QFile binaryDb(dbFileName + QLatin1String(".tmp")); + binaryDb.open(QIODevice::WriteOnly | QIODevice::Truncate); + db->stream = new QDataStream(&binaryDb); + *db->stream << DatabaseVersion << quint8(db->stream->version()) << fontpath; +// qDebug() << "creating binary database at" << binaryDb.fileName(); + + // Load in font definition file + FILE* fontdef=fopen(fontDirFile.toLocal8Bit().constData(),"r"); + if (fontdef) { + char buf[200]=""; + char name[200]=""; + char render[200]=""; + char file[200]=""; + char isitalic[10]=""; + char flags[10]=""; + do { + fgets(buf,200,fontdef); + if (buf[0] != '#') { + int weight=50; + int size=0; + sscanf(buf,"%s %s %s %s %d %d %s",name,file,render,isitalic,&weight,&size,flags); + QString filename; + if (file[0] != '/') + filename.append(fontpath).append(QLatin1Char('/')); + filename += QLatin1String(file); + bool italic = isitalic[0] == 'y'; + bool smooth = QByteArray(flags).contains('s'); + if (file[0] && QFile::exists(filename)) + db->addFont(QString::fromUtf8(name), /*foundry*/"", weight, italic, size/10, QFile::encodeName(filename), /*fileIndex*/ 0, smooth); + } + } while (!feof(fontdef)); + fclose(fontdef); + } + + + QDir dir(fontpath, QLatin1String("*.qpf")); + for (int i=0; iaddFont(familyname, /*foundry*/ "qt", weight, italic, pixelSize, QFile::encodeName(dir.absoluteFilePath(dir[i])), + /*fileIndex*/ 0, /*antialiased*/ true); + } + +#ifndef QT_NO_FREETYPE + dir.setNameFilters(QStringList() << QLatin1String("*.ttf") + << QLatin1String("*.ttc") << QLatin1String("*.pfa") + << QLatin1String("*.pfb")); + dir.refresh(); + for (int i = 0; i < int(dir.count()); ++i) { + const QByteArray file = QFile::encodeName(dir.absoluteFilePath(dir[i])); +// qDebug() << "looking at" << file; + db->addTTFile(file); + } +#endif + +#ifndef QT_NO_QWS_QPF2 + dir.setNameFilters(QStringList() << QLatin1String("*.qpf2")); + dir.refresh(); + for (int i = 0; i < int(dir.count()); ++i) { + const QByteArray file = QFile::encodeName(dir.absoluteFilePath(dir[i])); +// qDebug() << "looking at" << file; + db->addQPF2File(file); + } +#endif + +#else //QT_FONTS_ARE_RESOURCES +#ifdef QFONTDATABASE_DEBUG + { + QResource fontdir(fontpath); + FriendlyResource *fr = static_cast(&fontdir); + qDebug() << "fontdir" << fr->isValid() << fr->isDir() << fr->children(); + + } +#endif +#ifndef QT_NO_QWS_QPF2 + QDir dir(fontpath, QLatin1String("*.qpf2")); + for (int i = 0; i < int(dir.count()); ++i) { + const QByteArray file = QFile::encodeName(dir.absoluteFilePath(dir[i])); + //qDebug() << "looking at" << file; + db->addQPF2File(file); + } +#endif +#endif //QT_FONTS_ARE_RESOURCES + + +#ifdef QFONTDATABASE_DEBUG + // print the database + for (int f = 0; f < db->count; f++) { + QtFontFamily *family = db->families[f]; + FD_DEBUG("'%s' %s", qPrintable(family->name), (family->fixedPitch ? "fixed" : "")); +#if 0 + for (int i = 0; i < QFont::LastPrivateScript; ++i) { + FD_DEBUG("\t%s: %s", qPrintable(QFontDatabase::scriptName((QFont::Script) i)), + ((family->scripts[i] & QtFontFamily::Supported) ? "Supported" : + (family->scripts[i] & QtFontFamily::UnSupported) == QtFontFamily::UnSupported ? + "UnSupported" : "Unknown")); + } +#endif + + for (int fd = 0; fd < family->count; fd++) { + QtFontFoundry *foundry = family->foundries[fd]; + FD_DEBUG("\t\t'%s'", qPrintable(foundry->name)); + for (int s = 0; s < foundry->count; s++) { + QtFontStyle *style = foundry->styles[s]; + FD_DEBUG("\t\t\tstyle: style=%d weight=%d\n" + "\t\t\tstretch=%d", + style->key.style, style->key.weight, + style->key.stretch); + if (style->smoothScalable) + FD_DEBUG("\t\t\t\tsmooth scalable"); + else if (style->bitmapScalable) + FD_DEBUG("\t\t\t\tbitmap scalable"); + if (style->pixelSizes) { + FD_DEBUG("\t\t\t\t%d pixel sizes", style->count); + for (int z = 0; z < style->count; ++z) { + QtFontSize *size = style->pixelSizes + z; + FD_DEBUG("\t\t\t\t size %5d", + size->pixelSize); + } + } + } + } + } +#endif // QFONTDATABASE_DEBUG + +#ifndef QT_NO_LIBRARY + QStringList pluginFoundries = loader()->keys(); +// qDebug() << "plugin foundries:" << pluginFoundries; + for (int i = 0; i < pluginFoundries.count(); ++i) { + const QString foundry(pluginFoundries.at(i)); + + QFontEngineFactoryInterface *factory = qobject_cast(loader()->instance(foundry)); + if (!factory) { + qDebug() << "Could not load plugin for foundry" << foundry; + continue; + } + + QList fonts = factory->availableFontEngines(); + for (int i = 0; i < fonts.count(); ++i) { + QFontEngineInfo info = fonts.at(i); + + int weight = info.weight(); + if (weight <= 0) + weight = QFont::Normal; + + db->addFont(info.family(), foundry.toLatin1().constData(), + weight, info.style() != QFont::StyleNormal, + qRound(info.pixelSize()), /*file*/QByteArray(), + /*fileIndex*/0, /*antiAliased*/true, + info.writingSystems()); + } + } +#endif + +#ifndef QT_FONTS_ARE_RESOURCES + // the empty string/familyname signifies the end of the font list. + *db->stream << QString(); +#endif + { + bool coveredWritingSystems[QFontDatabase::WritingSystemsCount] = { 0 }; + + db->fallbackFamilies.clear(); + + for (int i = 0; i < db->count; ++i) { + QtFontFamily *family = db->families[i]; + bool add = false; + if (family->count == 0) + continue; + if (family->bogusWritingSystems) + continue; + for (int ws = 1; ws < QFontDatabase::WritingSystemsCount; ++ws) { + if (coveredWritingSystems[ws]) + continue; + if (family->writingSystems[ws] & QtFontFamily::Supported) { + coveredWritingSystems[ws] = true; + add = true; + } + } + if (add) + db->fallbackFamilies << family->name; + } + //qDebug() << "fallbacks on the server:" << db->fallbackFamilies; +#ifndef QT_FONTS_ARE_RESOURCES + *db->stream << db->fallbackFamilies; +#endif + } +#ifndef QT_FONTS_ARE_RESOURCES + delete db->stream; + db->stream = 0; + QFile::remove(dbFileName); + binaryDb.rename(dbFileName); +#endif +} + +// called from qwindowsystem_qws.cpp +void qt_qws_init_fontdb() +{ + initializeDb(); +} + +#ifndef QT_NO_SETTINGS +// called from qapplication_qws.cpp +void qt_applyFontDatabaseSettings(const QSettings &settings) +{ + initializeDb(); + QFontDatabasePrivate *db = privateDb(); + for (int i = 0; i < db->count; ++i) { + QtFontFamily *family = db->families[i]; + if (settings.contains(family->name)) + family->fallbackFamilies = settings.value(family->name).toStringList(); + } + + if (settings.contains(QLatin1String("Global Fallbacks"))) + db->fallbackFamilies = settings.value(QLatin1String("Global Fallbacks")).toStringList(); +} +#endif // QT_NO_SETTINGS + +static inline void load(const QString & = QString(), int = -1) +{ + initializeDb(); +} + +#ifndef QT_NO_FREETYPE + +#if (FREETYPE_MAJOR*10000+FREETYPE_MINOR*100+FREETYPE_PATCH) >= 20105 +#define X_SIZE(face,i) ((face)->available_sizes[i].x_ppem) +#define Y_SIZE(face,i) ((face)->available_sizes[i].y_ppem) +#else +#define X_SIZE(face,i) ((face)->available_sizes[i].width << 6) +#define Y_SIZE(face,i) ((face)->available_sizes[i].height << 6) +#endif + +#endif // QT_NO_FREETYPE + +static +QFontEngine *loadSingleEngine(int script, const QFontPrivate *fp, + const QFontDef &request, + QtFontFamily *family, QtFontFoundry *foundry, + QtFontStyle *style, QtFontSize *size) +{ + Q_UNUSED(script); + Q_UNUSED(fp); +#ifdef QT_NO_FREETYPE + Q_UNUSED(foundry); +#endif +#ifdef QT_NO_QWS_QPF + Q_UNUSED(family); +#endif + Q_ASSERT(size); + + int pixelSize = size->pixelSize; + if (!pixelSize || (style->smoothScalable && pixelSize == SMOOTH_SCALABLE)) + pixelSize = request.pixelSize; + +#ifndef QT_NO_QWS_QPF2 + if (foundry->name == QLatin1String("prerendered")) { +#ifdef QT_FONTS_ARE_RESOURCES + QResource res(QLatin1String(size->fileName.constData())); + if (res.isValid()) { + QFontEngineQPF *fe = new QFontEngineQPF(request, res.data(), res.size()); + if (fe->isValid()) + return fe; + delete fe; + qDebug() << "fontengine is not valid! " << size->fileName; + } else { + qDebug() << "Resource not valid" << size->fileName; + } +#else + int f = ::open(size->fileName, O_RDONLY, 0); + if (f >= 0) { + QFontEngineQPF *fe = new QFontEngineQPF(request, f); + if (fe->isValid()) + return fe; + delete fe; // will close f + qDebug() << "fontengine is not valid!"; + } +#endif + } else +#endif + if ( foundry->name != QLatin1String("qt") ) { ///#### is this the best way???? + QString file = QFile::decodeName(size->fileName); + + QFontDef def = request; + def.pixelSize = pixelSize; + +#ifdef QT_NO_QWS_SHARE_FONTS + bool shareFonts = false; +#else + static bool dontShareFonts = !qgetenv("QWS_NO_SHARE_FONTS").isEmpty(); + bool shareFonts = !dontShareFonts; +#endif + + QScopedPointer engine; + +#ifndef QT_NO_LIBRARY + QFontEngineFactoryInterface *factory = qobject_cast(loader()->instance(foundry->name)); + if (factory) { + QFontEngineInfo info; + info.setFamily(request.family); + info.setPixelSize(request.pixelSize); + info.setStyle(QFont::Style(request.style)); + info.setWeight(request.weight); + // #### antialiased + + QAbstractFontEngine *customEngine = factory->create(info); + if (customEngine) { + engine.reset(new QProxyFontEngine(customEngine, def)); + + if (shareFonts) { + QVariant hint = customEngine->fontProperty(QAbstractFontEngine::CacheGlyphsHint); + if (hint.isValid()) + shareFonts = hint.toBool(); + else + shareFonts = (pixelSize < 64); + } + } + } +#endif // QT_NO_LIBRARY + if ((engine.isNull() && !file.isEmpty() && QFile::exists(file)) || privateDb()->isApplicationFont(file)) { + QFontEngine::FaceId faceId; + faceId.filename = file.toLocal8Bit(); + faceId.index = size->fileIndex; + +#ifndef QT_NO_FREETYPE + + QScopedPointer fte(new QFontEngineFT(def)); + bool antialias = style->antialiased && !(request.styleStrategy & QFont::NoAntialias); + if (fte->init(faceId, antialias, + antialias ? QFontEngineFT::Format_A8 : QFontEngineFT::Format_Mono)) { +#ifdef QT_NO_QWS_QPF2 + return fte.take(); +#else + // try to distinguish between bdf and ttf fonts we can pre-render + // and don't try to share outline fonts + shareFonts = shareFonts + && !fte->defaultGlyphs()->outline_drawing + && !fte->getSfntTable(MAKE_TAG('h', 'e', 'a', 'd')).isEmpty(); + engine.reset(fte.take()); +#endif + } +#endif // QT_NO_FREETYPE + } + if (!engine.isNull()) { +#if !defined(QT_NO_QWS_QPF2) && !defined(QT_FONTS_ARE_RESOURCES) + if (shareFonts) { + QScopedPointer fe(new QFontEngineQPF(def, -1, engine.data())); + engine.take(); + if (fe->isValid()) + return fe.take(); + qWarning("Initializing QFontEngineQPF failed for %s", qPrintable(file)); + engine.reset(fe->takeRenderingEngine()); + } +#endif + return engine.take(); + } + } else + { +#ifndef QT_NO_QWS_QPF + QString fn = qwsFontPath(); + fn += QLatin1Char('/'); + fn += family->name.toLower() + + QLatin1Char('_') + QString::number(pixelSize*10) + + QLatin1Char('_') + QString::number(style->key.weight) + + (style->key.style == QFont::StyleItalic ? + QLatin1String("i.qpf") : QLatin1String(".qpf")); + //###rotation ### + + QFontEngine *fe = new QFontEngineQPF1(request, fn); + return fe; +#endif // QT_NO_QWS_QPF + } + return new QFontEngineBox(pixelSize); +} + +static +QFontEngine *loadEngine(int script, const QFontPrivate *fp, + const QFontDef &request, + QtFontFamily *family, QtFontFoundry *foundry, + QtFontStyle *style, QtFontSize *size) +{ + QScopedPointer engine(loadSingleEngine(script, fp, request, family, foundry, + style, size)); +#ifndef QT_NO_QWS_QPF + if (!engine.isNull() + && script == QUnicodeTables::Common + && !(request.styleStrategy & QFont::NoFontMerging) && !engine->symbol) { + + QStringList fallbacks = privateDb()->fallbackFamilies; + + if (family && !family->fallbackFamilies.isEmpty()) + fallbacks = family->fallbackFamilies; + + QFontEngine *fe = new QFontEngineMultiQWS(engine.data(), script, fallbacks); + engine.take(); + engine.reset(fe); + } +#endif + return engine.take(); +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt) +{ + QFontDatabasePrivate *db = privateDb(); +#ifdef QT_NO_FREETYPE + Q_UNUSED(fnt); +#else + fnt->families = db->addTTFile(QFile::encodeName(fnt->fileName), fnt->data); + db->fallbackFamilies += fnt->families; +#endif + db->reregisterAppFonts = true; +} + +bool QFontDatabase::removeApplicationFont(int handle) +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (handle < 0 || handle >= db->applicationFonts.count()) + return false; + + db->applicationFonts[handle] = QFontDatabasePrivate::ApplicationFont(); + + db->reregisterAppFonts = true; + db->invalidate(); + return true; +} + +bool QFontDatabase::removeAllApplicationFonts() +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (db->applicationFonts.isEmpty()) + return false; + + db->applicationFonts.clear(); + db->invalidate(); + return true; +} + +bool QFontDatabase::supportsThreadedFontRendering() +{ + return true; +} + +/*! + \internal +*/ +QFontEngine * +QFontDatabase::findFont(int script, const QFontPrivate *fp, + const QFontDef &request) +{ + QMutexLocker locker(fontDatabaseMutex()); + + const int force_encoding_id = -1; + + if (!privateDb()->count) + initializeDb(); + + QScopedPointer fe; + if (fp) { + if (fp->rawMode) { + fe.reset(loadEngine(script, fp, request, 0, 0, 0, 0)); + + // if we fail to load the rawmode font, use a 12pixel box engine instead + if (fe.isNull()) + fe.reset(new QFontEngineBox(12)); + return fe.take(); + } + + QFontCache::Key key(request, script); + fe.reset(QFontCache::instance()->findEngine(key)); + if (! fe.isNull()) + return fe.take(); + } + + QString family_name, foundry_name; + QtFontStyle::Key styleKey; + styleKey.style = request.style; + styleKey.weight = request.weight; + styleKey.stretch = request.stretch; + char pitch = request.ignorePitch ? '*' : request.fixedPitch ? 'm' : 'p'; + + parseFontName(request.family, foundry_name, family_name); + + FM_DEBUG("QFontDatabase::findFont\n" + " request:\n" + " family: %s [%s], script: %d\n" + " weight: %d, style: %d\n" + " stretch: %d\n" + " pixelSize: %g\n" + " pitch: %c", + family_name.isEmpty() ? "-- first in script --" : family_name.toLatin1().constData(), + foundry_name.isEmpty() ? "-- any --" : foundry_name.toLatin1().constData(), + script, request.weight, request.style, request.stretch, request.pixelSize, pitch); + + if (qt_enable_test_font && request.family == QLatin1String("__Qt__Box__Engine__")) { + fe.reset(new QTestFontEngine(request.pixelSize)); + fe->fontDef = request; + } + + if (fe.isNull()) + { + QtFontDesc desc; + match(script, request, family_name, foundry_name, force_encoding_id, &desc); + + if (desc.family != 0 && desc.foundry != 0 && desc.style != 0 + ) { + FM_DEBUG(" BEST:\n" + " family: %s [%s]\n" + " weight: %d, style: %d\n" + " stretch: %d\n" + " pixelSize: %d\n" + " pitch: %c\n" + " encoding: %d\n", + desc.family->name.toLatin1().constData(), + desc.foundry->name.isEmpty() ? "-- none --" : desc.foundry->name.toLatin1().constData(), + desc.style->key.weight, desc.style->key.style, + desc.style->key.stretch, desc.size ? desc.size->pixelSize : 0xffff, + 'p', 0 + ); + + fe.reset(loadEngine(script, fp, request, desc.family, desc.foundry, desc.style, desc.size + )); + } else { + FM_DEBUG(" NO MATCH FOUND\n"); + } + if (! fe.isNull()) + initFontDef(desc, request, &fe->fontDef); + } + +#ifndef QT_NO_FREETYPE + if (! fe.isNull()) { + if (scriptRequiresOpenType(script) && fe->type() == QFontEngine::Freetype) { + HB_Face hbFace = static_cast(fe.data())->harfbuzzFace(); + if (!hbFace || !hbFace->supported_scripts[script]) { + FM_DEBUG(" OpenType support missing for script\n"); + fe.reset(0); + } + } + } +#endif + + if (! fe.isNull()) { + if (fp) { + QFontDef def = request; + if (def.family.isEmpty()) { + def.family = fp->request.family; + def.family = def.family.left(def.family.indexOf(QLatin1Char(','))); + } + QFontCache::Key key(def, script); + QFontCache::instance()->insertEngine(key, fe.data()); + } + } + + if (fe.isNull()) { + if (!request.family.isEmpty()) + return 0; + + FM_DEBUG("returning box engine"); + + fe.reset(new QFontEngineBox(request.pixelSize)); + + if (fp) { + QFontCache::Key key(request, script); + QFontCache::instance()->insertEngine(key, fe.data()); + } + } + + if (fp && fp->dpi > 0) { + fe->fontDef.pointSize = qreal(double((fe->fontDef.pixelSize * 72) / fp->dpi)); + } else { + fe->fontDef.pointSize = request.pointSize; + } + + return fe.take(); +} + +void QFontDatabase::load(const QFontPrivate *d, int script) +{ + QFontDef req = d->request; + + if (req.pixelSize == -1) + req.pixelSize = qRound(req.pointSize*d->dpi/72); + if (req.pointSize < 0) + req.pointSize = req.pixelSize*72.0/d->dpi; + + if (!d->engineData) { + QFontCache::Key key(req, script); + + // look for the requested font in the engine data cache + d->engineData = QFontCache::instance()->findEngineData(key); + + if (!d->engineData) { + // create a new one + d->engineData = new QFontEngineData; + QT_TRY { + QFontCache::instance()->insertEngineData(key, d->engineData); + } QT_CATCH(...) { + delete d->engineData; + d->engineData = 0; + QT_RETHROW; + } + } else { + d->engineData->ref.ref(); + } + } + + // the cached engineData could have already loaded the engine we want + if (d->engineData->engines[script]) return; + + // double scale = 1.0; // ### TODO: fix the scale calculations + + // list of families to try + QStringList family_list; + + if (!req.family.isEmpty()) { + family_list = req.family.split(QLatin1Char(',')); + + // append the substitute list for each family in family_list + QStringList subs_list; + QStringList::ConstIterator it = family_list.constBegin(), end = family_list.constEnd(); + for (; it != end; ++it) + subs_list += QFont::substitutes(*it); + family_list += subs_list; + + // append the default fallback font for the specified script + // family_list << ... ; ########### + + // add the default family + QString defaultFamily = QApplication::font().family(); + if (! family_list.contains(defaultFamily)) + family_list << defaultFamily; + + // add QFont::defaultFamily() to the list, for compatibility with + // previous versions + family_list << QApplication::font().defaultFamily(); + } + + // null family means find the first font matching the specified script + family_list << QString(); + + // load the font + QFontEngine *engine = 0; + QStringList::ConstIterator it = family_list.constBegin(), end = family_list.constEnd(); + for (; !engine && it != end; ++it) { + req.family = *it; + + engine = QFontDatabase::findFont(script, d, req); + if (engine && (engine->type()==QFontEngine::Box) && !req.family.isEmpty()) + engine = 0; + } + + engine->ref.ref(); + d->engineData->engines[script] = engine; +} + + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontdatabase_s60.cpp b/src/gui/text/qfontdatabase_s60.cpp new file mode 100644 index 0000000000..1db4a7d359 --- /dev/null +++ b/src/gui/text/qfontdatabase_s60.cpp @@ -0,0 +1,1091 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "qdir.h" +#include "qfont_p.h" +#include "qfontengine_s60_p.h" +#include "qabstractfileengine.h" +#include "qdesktopservices.h" +#include "qtemporaryfile.h" +#include "qtextcodec.h" +#include +#include +#include "qendian.h" +#include +#ifdef QT_NO_FREETYPE +#include +#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS +#include // COpenFontRasterizer has moved to a new header file +#endif // SYMBIAN_ENABLE_SPLIT_HEADERS +#endif // QT_NO_FREETYPE + +QT_BEGIN_NAMESPACE + +QStringList qt_symbian_fontFamiliesOnFontServer() // Also used in qfont_s60.cpp +{ + QStringList result; + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + const int numTypeFaces = S60->screenDevice()->NumTypefaces(); + for (int i = 0; i < numTypeFaces; i++) { + TTypefaceSupport typefaceSupport; + S60->screenDevice()->TypefaceSupport(typefaceSupport, i); + const QString familyName((const QChar *)typefaceSupport.iTypeface.iName.Ptr(), typefaceSupport.iTypeface.iName.Length()); + result.append(familyName); + } + lock.relock(); + return result; +} + +QFileInfoList alternativeFilePaths(const QString &path, const QStringList &nameFilters, + QDir::Filters filters = QDir::NoFilter, QDir::SortFlags sort = QDir::NoSort, + bool uniqueFileNames = true) +{ + QFileInfoList result; + + // Prepare a 'soft to hard' drive list: W:, X: ... A:, Z: + QStringList driveStrings; + foreach (const QFileInfo &drive, QDir::drives()) + driveStrings.append(drive.absolutePath()); + driveStrings.sort(); + const QString zDriveString(QLatin1String("Z:/")); + driveStrings.removeAll(zDriveString); + driveStrings.prepend(zDriveString); + + QStringList uniqueFileNameList; + for (int i = driveStrings.count() - 1; i >= 0; --i) { + const QDir dirOnDrive(driveStrings.at(i) + path); + const QFileInfoList entriesOnDrive = dirOnDrive.entryInfoList(nameFilters, filters, sort); + if (uniqueFileNames) { + foreach(const QFileInfo &entry, entriesOnDrive) { + if (!uniqueFileNameList.contains(entry.fileName())) { + uniqueFileNameList.append(entry.fileName()); + result.append(entry); + } + } + } else { + result.append(entriesOnDrive); + } + } + return result; +} + +#ifdef QT_NO_FREETYPE +class QSymbianFontDatabaseExtrasImplementation : public QSymbianFontDatabaseExtras +{ +public: + QSymbianFontDatabaseExtrasImplementation(); + ~QSymbianFontDatabaseExtrasImplementation(); + + const QSymbianTypeFaceExtras *extras(const QString &typeface, bool bold, bool italic) const; + void removeAppFontData(QFontDatabasePrivate::ApplicationFont *fnt); + static inline bool appFontLimitReached(); + TUid addFontFileToFontStore(const QFileInfo &fontFileInfo); + static void clear(); + + static inline QString tempAppFontFolder(); + static const QString appFontMarkerPrefix; + static QString appFontMarker(); // 'qaf' + + struct CFontFromFontStoreReleaser { + static inline void cleanup(CFont *font) + { + if (!font) + return; + const QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast(privateDb()->symbianExtras); + dbExtras->m_store->ReleaseFont(font); + } + }; + + struct CFontFromScreenDeviceReleaser { + static inline void cleanup(CFont *font) + { + if (!font) + return; + S60->screenDevice()->ReleaseFont(font); + } + }; + +// m_heap, m_store, m_rasterizer and m_extras are used if Symbian +// does not provide the Font Table API + RHeap* m_heap; + CFontStore *m_store; + COpenFontRasterizer *m_rasterizer; + mutable QList m_extras; + + mutable QHash m_extrasHash; + mutable QSet m_applicationFontFamilies; +}; + +const QString QSymbianFontDatabaseExtrasImplementation::appFontMarkerPrefix = + QLatin1String("Q"); + +inline QString QSymbianFontDatabaseExtrasImplementation::tempAppFontFolder() +{ + return QDir::toNativeSeparators(QDir::tempPath()) + QLatin1Char('\\'); +} + +QString QSymbianFontDatabaseExtrasImplementation::appFontMarker() +{ + static QString result; + if (result.isEmpty()) { + quint16 id = 0; + if (QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) { + // We are allowed to load app fonts even from previous, crashed runs + // of this application, since we can access the font tables. + const quint32 uid = RProcess().Type().MostDerived().iUid; + id = static_cast(uid + (uid >> 16)); + } else { + // If no font table Api is available, we must not even load a font + // from a previous (crashed) run of this application. Reason: we + // won't get the font tables, they are not in the CFontStore. + // So, we use the pid, for more uniqueness. + id = static_cast(RProcess().Id().Id()); + } + result = appFontMarkerPrefix + QString::fromLatin1("%1").arg(id & 0x7fff, 3, 32, QLatin1Char('0')); + Q_ASSERT(appFontMarkerPrefix.length() == 1 && result.length() == 4); + } + return result; +} + +static inline bool qt_symbian_fontNameHasAppFontMarker(const QString &fontName) +{ + const int idLength = 3; // Keep in sync with id length in appFontMarker(). + const QString &prefix = QSymbianFontDatabaseExtrasImplementation::appFontMarkerPrefix; + if (fontName.length() < prefix.length() + idLength + || fontName.mid(fontName.length() - idLength - prefix.length(), prefix.length()) != prefix) + return false; + // Testing if the the id is base32 data + for (int i = fontName.length() - idLength; i < fontName.length(); ++i) { + const QChar &c = fontName.at(i); + if (!(c >= QLatin1Char('0') && c <= QLatin1Char('9') + || c >= QLatin1Char('a') && c <= QLatin1Char('v'))) + return false; + } + return true; +} + +// If fontName is an application font of this app, prepend the app font marker +QString qt_symbian_fontNameWithAppFontMarker(const QString &fontName) +{ + QFontDatabasePrivate *db = privateDb(); + Q_ASSERT(db); + const QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast(db->symbianExtras); + return dbExtras->m_applicationFontFamilies.contains(fontName) ? + fontName + QSymbianFontDatabaseExtrasImplementation::appFontMarker() + : fontName; +} + +static inline QString qt_symbian_appFontNameWithoutMarker(const QString &markedFontName) +{ + return markedFontName.left(markedFontName.length() + - QSymbianFontDatabaseExtrasImplementation::appFontMarker().length()); +} + +QSymbianFontDatabaseExtrasImplementation::QSymbianFontDatabaseExtrasImplementation() +{ + if (!QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) { + QStringList filters; + filters.append(QLatin1String("*.ttf")); + filters.append(QLatin1String("*.ccc")); + filters.append(QLatin1String("*.ltt")); + const QFileInfoList fontFiles = alternativeFilePaths(QLatin1String("resource\\Fonts"), filters); + + const TInt heapMinLength = 0x1000; + const TInt heapMaxLength = qMax(0x20000 * fontFiles.count(), heapMinLength); + m_heap = User::ChunkHeap(NULL, heapMinLength, heapMaxLength); + QT_TRAP_THROWING( + m_store = CFontStore::NewL(m_heap); + m_rasterizer = COpenFontRasterizer::NewL(TUid::Uid(0x101F7F5E)); + CleanupStack::PushL(m_rasterizer); + m_store->InstallRasterizerL(m_rasterizer); + CleanupStack::Pop(m_rasterizer);); + + foreach (const QFileInfo &fontFileInfo, fontFiles) + addFontFileToFontStore(fontFileInfo); + } +} + +void QSymbianFontDatabaseExtrasImplementation::clear() +{ + QFontDatabasePrivate *db = privateDb(); + if (!db) + return; + const QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast(db->symbianExtras); + if (!dbExtras) + return; // initializeDb() has never been called + if (QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) { + qDeleteAll(dbExtras->m_extrasHash); + } else { + typedef QList::iterator iterator; + for (iterator p = dbExtras->m_extras.begin(); p != dbExtras->m_extras.end(); ++p) { + dbExtras->m_store->ReleaseFont((*p)->fontOwner()); + delete *p; + } + dbExtras->m_extras.clear(); + } + dbExtras->m_extrasHash.clear(); +} + +void qt_cleanup_symbianFontDatabase() +{ + QFontDatabasePrivate *db = privateDb(); + if (!db) + return; + + QSymbianFontDatabaseExtrasImplementation::clear(); + + if (!db->applicationFonts.isEmpty()) { + QFontDatabase::removeAllApplicationFonts(); + // We remove the left over temporary font files of Qt application. + // Active fonts are undeletable since the font server holds a handle + // on them, so we do not need to worry to delete other running + // applications' fonts. + const QDir dir(QSymbianFontDatabaseExtrasImplementation::tempAppFontFolder()); + const QStringList filter( + QSymbianFontDatabaseExtrasImplementation::appFontMarkerPrefix + QLatin1String("*.ttf")); + foreach (const QFileInfo &ttfFile, dir.entryInfoList(filter)) + QFile(ttfFile.absoluteFilePath()).remove(); + db->applicationFonts.clear(); + } +} + +QSymbianFontDatabaseExtrasImplementation::~QSymbianFontDatabaseExtrasImplementation() +{ + qt_cleanup_symbianFontDatabase(); + if (!QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) { + delete m_store; + m_heap->Close(); + } +} + +#ifndef FNTSTORE_H_INLINES_SUPPORT_FMM +/* + Workaround: fntstore.h has an inlined function 'COpenFont* CBitmapFont::OpenFont()' + that returns a private data member. The header will change between SDKs. But Qt has + to build on any SDK version and run on other versions of Symbian OS. + This function performs the needed pointer arithmetic to get the right COpenFont* +*/ +COpenFont* OpenFontFromBitmapFont(const CBitmapFont* aBitmapFont) +{ + const TInt offsetIOpenFont = 92; // '_FOFF(CBitmapFont, iOpenFont)' ..if iOpenFont weren't private + const TUint valueIOpenFont = *(TUint*)PtrAdd(aBitmapFont, offsetIOpenFont); + return (valueIOpenFont & 1) ? + (COpenFont*)PtrAdd(aBitmapFont, valueIOpenFont & ~1) : // New behavior: iOpenFont is offset + (COpenFont*)valueIOpenFont; // Old behavior: iOpenFont is pointer +} +#endif // FNTSTORE_H_INLINES_SUPPORT_FMM + +const QSymbianTypeFaceExtras *QSymbianFontDatabaseExtrasImplementation::extras(const QString &aTypeface, + bool bold, bool italic) const +{ + const QString typeface = qt_symbian_fontNameWithAppFontMarker(aTypeface); + const QString searchKey = typeface + QString::number(int(bold)) + QString::number(int(italic)); + if (!m_extrasHash.contains(searchKey)) { + TFontSpec searchSpec(qt_QString2TPtrC(typeface), 1); + if (bold) + searchSpec.iFontStyle.SetStrokeWeight(EStrokeWeightBold); + if (italic) + searchSpec.iFontStyle.SetPosture(EPostureItalic); + + CFont* font = NULL; + if (QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) { + const TInt err = S60->screenDevice()->GetNearestFontToDesignHeightInPixels(font, searchSpec); + Q_ASSERT(err == KErrNone && font); + QScopedPointer sFont(font); + QSymbianTypeFaceExtras *extras = new QSymbianTypeFaceExtras(font); + sFont.take(); + m_extrasHash.insert(searchKey, extras); + } else { + const TInt err = m_store->GetNearestFontToDesignHeightInPixels(font, searchSpec); + Q_ASSERT(err == KErrNone && font); + const CBitmapFont *bitmapFont = static_cast(font); + COpenFont *openFont = +#ifdef FNTSTORE_H_INLINES_SUPPORT_FMM + bitmapFont->OpenFont(); +#else // FNTSTORE_H_INLINES_SUPPORT_FMM + OpenFontFromBitmapFont(bitmapFont); +#endif // FNTSTORE_H_INLINES_SUPPORT_FMM + const TOpenFontFaceAttrib* const attrib = openFont->FaceAttrib(); + const QString foundKey = + QString((const QChar*)attrib->FullName().Ptr(), attrib->FullName().Length()); + if (!m_extrasHash.contains(foundKey)) { + QScopedPointer sFont(font); + QSymbianTypeFaceExtras *extras = new QSymbianTypeFaceExtras(font, openFont); + sFont.take(); + m_extras.append(extras); + m_extrasHash.insert(searchKey, extras); + m_extrasHash.insert(foundKey, extras); + } else { + m_store->ReleaseFont(font); + m_extrasHash.insert(searchKey, m_extrasHash.value(foundKey)); + } + } + } + return m_extrasHash.value(searchKey); +} + +void QSymbianFontDatabaseExtrasImplementation::removeAppFontData( + QFontDatabasePrivate::ApplicationFont *fnt) +{ + clear(); + if (!QSymbianTypeFaceExtras::symbianFontTableApiAvailable() + && fnt->fontStoreFontFileUid.iUid != 0) + m_store->RemoveFile(fnt->fontStoreFontFileUid); + if (!fnt->families.isEmpty()) + m_applicationFontFamilies.remove(fnt->families.first()); + if (fnt->screenDeviceFontFileId != 0) + S60->screenDevice()->RemoveFile(fnt->screenDeviceFontFileId); + QFile::remove(fnt->temporaryFileName); + *fnt = QFontDatabasePrivate::ApplicationFont(); +} + +bool QSymbianFontDatabaseExtrasImplementation::appFontLimitReached() +{ + QFontDatabasePrivate *db = privateDb(); + if (!db) + return false; + const int maxAppFonts = 5; + int registeredAppFonts = 0; + foreach (const QFontDatabasePrivate::ApplicationFont &appFont, db->applicationFonts) + if (!appFont.families.isEmpty() && ++registeredAppFonts == maxAppFonts) + return true; + return false; +} + +TUid QSymbianFontDatabaseExtrasImplementation::addFontFileToFontStore(const QFileInfo &fontFileInfo) +{ + Q_ASSERT(!QSymbianTypeFaceExtras::symbianFontTableApiAvailable()); + const QString fontFile = QDir::toNativeSeparators(fontFileInfo.absoluteFilePath()); + const TPtrC fontFilePtr(qt_QString2TPtrC(fontFile)); + TUid fontUid = {0}; + TRAP_IGNORE(fontUid = m_store->AddFileL(fontFilePtr)); + return fontUid; +} + +#else // QT_NO_FREETYPE +class QFontEngineFTS60 : public QFontEngineFT +{ +public: + QFontEngineFTS60(const QFontDef &fd); +}; + +QFontEngineFTS60::QFontEngineFTS60(const QFontDef &fd) + : QFontEngineFT(fd) +{ + default_hint_style = HintFull; +} +#endif // QT_NO_FREETYPE + +/* + QFontEngineS60::pixelsToPoints, QFontEngineS60::pointsToPixels, QFontEngineMultiS60::QFontEngineMultiS60 + and QFontEngineMultiS60::QFontEngineMultiS60 should be in qfontengine_s60.cpp. But since also the + Freetype based font rendering need them, they are here. +*/ +qreal QFontEngineS60::pixelsToPoints(qreal pixels, Qt::Orientation orientation) +{ + CWsScreenDevice* device = S60->screenDevice(); + return (orientation == Qt::Horizontal? + device->HorizontalPixelsToTwips(pixels) + :device->VerticalPixelsToTwips(pixels)) / KTwipsPerPoint; +} + +qreal QFontEngineS60::pointsToPixels(qreal points, Qt::Orientation orientation) +{ + CWsScreenDevice* device = S60->screenDevice(); + const int twips = points * KTwipsPerPoint; + return orientation == Qt::Horizontal? + device->HorizontalTwipsToPixels(twips) + :device->VerticalTwipsToPixels(twips); +} + +QFontEngineMultiS60::QFontEngineMultiS60(QFontEngine *first, int script, const QStringList &fallbackFamilies) + : QFontEngineMulti(fallbackFamilies.size() + 1) + , m_script(script) + , m_fallbackFamilies(fallbackFamilies) +{ + engines[0] = first; + first->ref.ref(); + fontDef = engines[0]->fontDef; +} + +void QFontEngineMultiS60::loadEngine(int at) +{ + Q_ASSERT(at < engines.size()); + Q_ASSERT(engines.at(at) == 0); + + QFontDef request = fontDef; + request.styleStrategy |= QFont::NoFontMerging; + request.family = m_fallbackFamilies.at(at-1); + engines[at] = QFontDatabase::findFont(m_script, + /*fontprivate*/0, + request); + Q_ASSERT(engines[at]); +} + +#ifdef QT_NO_FREETYPE +static bool registerScreenDeviceFont(int screenDeviceFontIndex, + const QSymbianFontDatabaseExtrasImplementation *dbExtras) +{ + TTypefaceSupport typefaceSupport; + S60->screenDevice()->TypefaceSupport(typefaceSupport, screenDeviceFontIndex); + + QString familyName((const QChar*)typefaceSupport.iTypeface.iName.Ptr(), typefaceSupport.iTypeface.iName.Length()); + if (qt_symbian_fontNameHasAppFontMarker(familyName)) { + const QString &marker = QSymbianFontDatabaseExtrasImplementation::appFontMarker(); + if (familyName.endsWith(marker)) { + familyName = qt_symbian_appFontNameWithoutMarker(familyName); + dbExtras->m_applicationFontFamilies.insert(familyName); + } else { + return false; // This was somebody else's application font. Skip it. + } + } + + CFont *font; // We have to get a font instance in order to know all the details + TFontSpec fontSpec(typefaceSupport.iTypeface.iName, 11); + if (S60->screenDevice()->GetNearestFontInPixels(font, fontSpec) != KErrNone) + return false; + QScopedPointer sFont(font); + if (font->TypeUid() != KCFbsFontUid) + return false; + TOpenFontFaceAttrib faceAttrib; + const CFbsFont *cfbsFont = static_cast(font); + cfbsFont->GetFaceAttrib(faceAttrib); + + QtFontStyle::Key styleKey; + styleKey.style = faceAttrib.IsItalic()?QFont::StyleItalic:QFont::StyleNormal; + styleKey.weight = faceAttrib.IsBold()?QFont::Bold:QFont::Normal; + + QtFontFamily *family = privateDb()->family(familyName, true); + family->fixedPitch = faceAttrib.IsMonoWidth(); + QtFontFoundry *foundry = family->foundry(QString(), true); + QtFontStyle *style = foundry->style(styleKey, true); + style->smoothScalable = typefaceSupport.iIsScalable; + style->pixelSize(0, true); + + const QSymbianTypeFaceExtras *typeFaceExtras = + dbExtras->extras(familyName, faceAttrib.IsBold(), faceAttrib.IsItalic()); + const QByteArray os2Table = typeFaceExtras->getSfntTable(MAKE_TAG('O', 'S', '/', '2')); + const unsigned char* data = reinterpret_cast(os2Table.constData()); + const unsigned char* ulUnicodeRange = data + 42; + quint32 unicodeRange[4] = { + qFromBigEndian(ulUnicodeRange), + qFromBigEndian(ulUnicodeRange + 4), + qFromBigEndian(ulUnicodeRange + 8), + qFromBigEndian(ulUnicodeRange + 12) + }; + const unsigned char* ulCodePageRange = data + 78; + quint32 codePageRange[2] = { + qFromBigEndian(ulCodePageRange), + qFromBigEndian(ulCodePageRange + 4) + }; + const QList writingSystems = + qt_determine_writing_systems_from_truetype_bits(unicodeRange, codePageRange); + foreach (const QFontDatabase::WritingSystem system, writingSystems) + family->writingSystems[system] = QtFontFamily::Supported; + return true; +} +#endif + +static void initializeDb() +{ + QFontDatabasePrivate *db = privateDb(); + if(!db || db->count) + return; + +#ifdef QT_NO_FREETYPE + if (!db->symbianExtras) + db->symbianExtras = new QSymbianFontDatabaseExtrasImplementation; + + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + + const int numTypeFaces = S60->screenDevice()->NumTypefaces(); + const QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast(db->symbianExtras); + for (int i = 0; i < numTypeFaces; i++) + registerScreenDeviceFont(i, dbExtras); + + // We have to clear/release all CFonts, here, in case one of the fonts is + // an application font of another running Qt app. Otherwise the other Qt app + // cannot remove it's application font, anymore -> "Zombie Font". + QSymbianFontDatabaseExtrasImplementation::clear(); + + lock.relock(); + +#else // QT_NO_FREETYPE + QDir dir(QDesktopServices::storageLocation(QDesktopServices::FontsLocation)); + dir.setNameFilters(QStringList() << QLatin1String("*.ttf") + << QLatin1String("*.ttc") << QLatin1String("*.pfa") + << QLatin1String("*.pfb")); + for (int i = 0; i < int(dir.count()); ++i) { + const QByteArray file = QFile::encodeName(dir.absoluteFilePath(dir[i])); + db->addTTFile(file); + } +#endif // QT_NO_FREETYPE +} + +static inline void load(const QString &family = QString(), int script = -1) +{ + Q_UNUSED(family) + Q_UNUSED(script) + initializeDb(); +} + +struct OffsetTable { + quint32 sfntVersion; + quint16 numTables, searchRange, entrySelector, rangeShift; +}; + +struct TableRecord { + quint32 tag, checkSum, offset, length; +}; + +struct NameTableHead { + quint16 format, count, stringOffset; +}; + +struct NameRecord { + quint16 platformID, encodingID, languageID, nameID, length, offset; +}; + +static quint32 ttfCalcChecksum(const char *data, quint32 bytesCount) +{ + quint32 result = 0; + const quint32 *ptr = reinterpret_cast(data); + const quint32 *endPtr = + ptr + (bytesCount + sizeof(quint32) - 1) / sizeof(quint32); + while (ptr < endPtr) { + const quint32 unit32Value = *ptr++; + result += qFromBigEndian(unit32Value); + } + return result; +} + +static inline quint32 toDWordBoundary(quint32 value) +{ + return (value + 3) & ~3; +} + +static inline quint32 dWordPadding(quint32 value) +{ + return (4 - (value & 3)) & 3; +} + +static inline bool ttfMarkNameTable(QByteArray &table, const QString &marker) +{ + const quint32 tableLength = static_cast(table.size()); + + if (tableLength > 50000 // hard limit + || tableLength < sizeof(NameTableHead)) // corrupt name table + return false; + + const NameTableHead *head = reinterpret_cast(table.constData()); + const quint16 count = qFromBigEndian(head->count); + const quint16 stringOffset = qFromBigEndian(head->stringOffset); + if (count > 200 // hard limit + || stringOffset >= tableLength // corrupt name table + || sizeof(NameTableHead) + count * sizeof(NameRecord) >= tableLength) // corrupt name table + return false; + + QTextEncoder encoder(QTextCodec::codecForName("UTF-16BE"), QTextCodec::IgnoreHeader); + const QByteArray markerUtf16BE = encoder.fromUnicode(marker); + const QByteArray markerAscii = marker.toAscii(); + + QByteArray markedTable; + markedTable.reserve(tableLength + marker.length() * 20); // Original size plus some extra + markedTable.append(table, stringOffset); + QByteArray markedStrings; + quint32 stringDataCount = stringOffset; + for (quint16 i = 0; i < count; ++i) { + const quint32 nameRecordOffset = sizeof(NameTableHead) + sizeof(NameRecord) * i; + NameRecord *nameRecord = + reinterpret_cast(markedTable.data() + nameRecordOffset); + const quint16 nameID = qFromBigEndian(nameRecord->nameID); + const quint16 platformID = qFromBigEndian(nameRecord->platformID); + const quint16 encodingID = qFromBigEndian(nameRecord->encodingID); + const quint16 offset = qFromBigEndian(nameRecord->offset); + const quint16 length = qFromBigEndian(nameRecord->length); + stringDataCount += length; + if (stringDataCount > 80000 // hard limit. String data may be > name table size. Multiple records can reference the same string. + || static_cast(stringOffset + offset + length) > tableLength) // String outside bounds + return false; + const bool needsMarker = + nameID == 1 || nameID == 3 || nameID == 4 || nameID == 16 || nameID == 21; + const bool isUnicode = + platformID == 0 || platformID == 3 && encodingID == 1; + const QByteArray originalString = + QByteArray::fromRawData(table.constData() + stringOffset + offset, length); + QByteArray markedString; + if (needsMarker) { + const int maxBytesLength = (KMaxTypefaceNameLength - marker.length()) * (isUnicode ? 2 : 1); + markedString = originalString.left(maxBytesLength) + (isUnicode ? markerUtf16BE : markerAscii); + } else { + markedString = originalString; + } + nameRecord->offset = qToBigEndian(static_cast(markedStrings.length())); + nameRecord->length = qToBigEndian(static_cast(markedString.length())); + markedStrings.append(markedString); + } + markedTable.append(markedStrings); + table = markedTable; + return true; +} + +const quint32 ttfMaxFileSize = 3500000; + +static inline bool ttfMarkAppFont(QByteArray &ttf, const QString &marker) +{ + const quint32 ttfChecksumNumber = 0xb1b0afba; + const quint32 alignment = 4; + const quint32 ttfLength = static_cast(ttf.size()); + if (ttfLength > ttfMaxFileSize // hard limit + || ttfLength % alignment != 0 // ttf sizes are always factors of 4 + || ttfLength <= sizeof(OffsetTable) // ttf too short + || ttfCalcChecksum(ttf.constData(), ttf.size()) != ttfChecksumNumber) // ttf checksum is invalid + return false; + + const OffsetTable *offsetTable = reinterpret_cast(ttf.constData()); + const quint16 numTables = qFromBigEndian(offsetTable->numTables); + const quint32 recordsLength = + toDWordBoundary(sizeof(OffsetTable) + numTables * sizeof(TableRecord)); + if (numTables > 30 // hard limit + || recordsLength + numTables * alignment > ttfLength) // Corrupt ttf. Tables would not fit, even if empty. + return false; + + QByteArray markedTtf; + markedTtf.reserve(ttfLength + marker.length() * 20); // Original size plus some extra + markedTtf.append(ttf.constData(), recordsLength); + + const quint32 ttfCheckSumAdjustmentOffset = 8; // Offset from the start of 'head' + int indexOfHeadTable = -1; + quint32 ttfDataSize = recordsLength; + typedef QPair Range; + QList memoryRanges; + memoryRanges.reserve(numTables); + for (int i = 0; i < numTables; ++i) { + TableRecord *tableRecord = + reinterpret_cast(markedTtf.data() + sizeof(OffsetTable) + i * sizeof(TableRecord)); + const quint32 offset = qFromBigEndian(tableRecord->offset); + const quint32 length = qFromBigEndian(tableRecord->length); + const quint32 lengthAligned = toDWordBoundary(length); + ttfDataSize += lengthAligned; + if (offset < recordsLength // must not intersect ttf header/records + || offset % alignment != 0 // must be aligned + || offset > ttfLength - alignment // table out of bounds + || offset + lengthAligned > ttfLength // table out of bounds + || ttfDataSize > ttfLength) // tables would not fit into the ttf + return false; + + foreach (const Range &range, memoryRanges) + if (offset < range.first + range.second && offset + lengthAligned > range.first) + return false; // Overlaps with another table + memoryRanges.append(Range(offset, lengthAligned)); + + quint32 checkSum = qFromBigEndian(tableRecord->checkSum); + if (tableRecord->tag == qToBigEndian(static_cast('head'))) { + if (length < ttfCheckSumAdjustmentOffset + sizeof(quint32)) + return false; // Invalid 'head' table + const quint32 *checkSumAdjustmentTag = + reinterpret_cast(ttf.constData() + offset + ttfCheckSumAdjustmentOffset); + const quint32 checkSumAdjustment = qFromBigEndian(*checkSumAdjustmentTag); + checkSum += checkSumAdjustment; + indexOfHeadTable = i; // For the ttf checksum re-calculation, later + } + if (checkSum != ttfCalcChecksum(ttf.constData() + offset, length)) + return false; // Table checksum is invalid + + bool updateTableChecksum = false; + QByteArray table; + if (tableRecord->tag == qToBigEndian(static_cast('name'))) { + table = QByteArray(ttf.constData() + offset, length); + if (!ttfMarkNameTable(table, marker)) + return false; // Name table was not markable. + updateTableChecksum = true; + } else { + table = QByteArray::fromRawData(ttf.constData() + offset, length); + } + + tableRecord->offset = qToBigEndian(markedTtf.size()); + tableRecord->length = qToBigEndian(table.size()); + markedTtf.append(table); + markedTtf.append(QByteArray(dWordPadding(table.size()), 0)); // 0-padding + if (updateTableChecksum) { + TableRecord *tableRecord = // Need to recalculate, since markedTtf changed + reinterpret_cast(markedTtf.data() + sizeof(OffsetTable) + i * sizeof(TableRecord)); + const quint32 offset = qFromBigEndian(tableRecord->offset); + const quint32 length = qFromBigEndian(tableRecord->length); + tableRecord->checkSum = qToBigEndian(ttfCalcChecksum(markedTtf.constData() + offset, length)); + } + } + if (indexOfHeadTable == -1 // 'head' table is mandatory + || ttfDataSize != ttfLength) // We do not allow ttf data "holes". Neither does Symbian. + return false; + TableRecord *headRecord = + reinterpret_cast(markedTtf.data() + sizeof(OffsetTable) + indexOfHeadTable * sizeof(TableRecord)); + quint32 *checkSumAdjustmentTag = + reinterpret_cast(markedTtf.data() + qFromBigEndian(headRecord->offset) + ttfCheckSumAdjustmentOffset); + *checkSumAdjustmentTag = 0; + const quint32 ttfChecksum = ttfCalcChecksum(markedTtf.constData(), markedTtf.count()); + *checkSumAdjustmentTag = qToBigEndian(ttfChecksumNumber - ttfChecksum); + ttf = markedTtf; + return true; +} + +static inline bool ttfCanSymbianLoadFont(const QByteArray &data, const QString &fileName) +{ + bool result = false; + QString ttfFileName; + QFile tempFileGuard; + QFileInfo info(fileName); + if (!data.isEmpty()) { + QTemporaryFile tempfile(QSymbianFontDatabaseExtrasImplementation::tempAppFontFolder() + + QSymbianFontDatabaseExtrasImplementation::appFontMarker() + + QLatin1String("XXXXXX.ttf")); + if (!tempfile.open() || tempfile.write(data) == -1) + return false; + ttfFileName = QDir::toNativeSeparators(QFileInfo(tempfile).canonicalFilePath()); + tempfile.setAutoRemove(false); + tempfile.close(); + tempFileGuard.setFileName(ttfFileName); + if (!tempFileGuard.open(QIODevice::ReadOnly)) + return false; + } else if (info.isFile()) { + ttfFileName = QDir::toNativeSeparators(info.canonicalFilePath()); + } else { + return false; + } + + CFontStore *store = 0; + RHeap* heap = User::ChunkHeap(NULL, 0x1000, 0x20000); + if (heap) { + QT_TRAP_THROWING( + CleanupClosePushL(*heap); + store = CFontStore::NewL(heap); + CleanupStack::PushL(store); + COpenFontRasterizer *rasterizer = COpenFontRasterizer::NewL(TUid::Uid(0x101F7F5E)); + CleanupStack::PushL(rasterizer); + store->InstallRasterizerL(rasterizer); + CleanupStack::Pop(rasterizer); + TUid fontUid = {-1}; + TRAP_IGNORE(fontUid = store->AddFileL(qt_QString2TPtrC(ttfFileName))); + if (fontUid.iUid != -1) + result = true; + CleanupStack::PopAndDestroy(2, heap); // heap, store + ); + } + + if (tempFileGuard.isOpen()) + tempFileGuard.remove(); + + return result; +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt) +{ + if (QSymbianFontDatabaseExtrasImplementation::appFontLimitReached() + || fnt->data.size() > ttfMaxFileSize // hard limit + || fnt->data.isEmpty() && (!fnt->fileName.endsWith(QLatin1String(".ttf"), Qt::CaseInsensitive) // Only buffer or .ttf + || QFileInfo(fnt->fileName).size() > ttfMaxFileSize)) // hard limit + return; + +// Using ttfCanSymbianLoadFont() causes crashes on app destruction (Symbian^3|PR1 and lower). +// Therefore, not using it for now, but eventually in a later version. +// if (!ttfCanSymbianLoadFont(fnt->data, fnt->fileName)) +// return; + + QFontDatabasePrivate *db = privateDb(); + if (!db) + return; + + if (!db->count) + initializeDb(); + + QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast(db->symbianExtras); + if (!dbExtras) + return; + + const QString &marker = QSymbianFontDatabaseExtrasImplementation::appFontMarker(); + + // The QTemporaryFile object being used in the following section must be + // destructed before letting Symbian load the TTF file. Symbian would not + // load it otherwise, because QTemporaryFile will still keep some handle + // on it. The scope is used to reduce the life time of the QTemporaryFile. + // In order to prevent other processes from modifying the file between the + // moment where the QTemporaryFile is destructed and the file is loaded by + // Symbian, we have a QFile "tempFileGuard" outside the scope which opens + // the file in ReadOnly mode while the QTemporaryFile is still alive. + QFile tempFileGuard; + { + QTemporaryFile tempfile(QSymbianFontDatabaseExtrasImplementation::tempAppFontFolder() + + marker + QLatin1String("XXXXXX.ttf")); + if (!tempfile.open()) + return; + const QString tempFileName = QFileInfo(tempfile).canonicalFilePath(); + if (fnt->data.isEmpty()) { + QFile sourceFile(fnt->fileName); + if (!sourceFile.open(QIODevice::ReadOnly)) + return; + fnt->data = sourceFile.readAll(); + } + if (!ttfMarkAppFont(fnt->data, marker) || tempfile.write(fnt->data) == -1) + return; + tempfile.setAutoRemove(false); + tempfile.close(); // Tempfile still keeps a file handle, forbidding write access + fnt->data.clear(); // The TTF data was marked and saved. Not needed in memory, anymore. + tempFileGuard.setFileName(tempFileName); + if (!tempFileGuard.open(QIODevice::ReadOnly)) + return; + fnt->temporaryFileName = tempFileName; + } + + const QString fullFileName = QDir::toNativeSeparators(fnt->temporaryFileName); + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + const QStringList fontsOnServerBefore = qt_symbian_fontFamiliesOnFontServer(); + const TInt err = + S60->screenDevice()->AddFile(qt_QString2TPtrC(fullFileName), fnt->screenDeviceFontFileId); + tempFileGuard.close(); // Did its job + const QStringList fontsOnServerAfter = qt_symbian_fontFamiliesOnFontServer(); + if (err == KErrNone && fontsOnServerBefore.count() < fontsOnServerAfter.count()) { // Added to screen device? + int fontOnServerIndex = fontsOnServerAfter.count() - 1; + for (int i = 0; i < fontsOnServerBefore.count(); i++) { + if (fontsOnServerBefore.at(i) != fontsOnServerAfter.at(i)) { + fontOnServerIndex = i; + break; + } + } + + // Must remove all font engines with their CFonts, first. + QFontCache::instance()->clear(); + db->free(); + QSymbianFontDatabaseExtrasImplementation::clear(); + + if (!QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) + fnt->fontStoreFontFileUid = dbExtras->addFontFileToFontStore(QFileInfo(fullFileName)); + + const QString &appFontName = fontsOnServerAfter.at(fontOnServerIndex); + fnt->families.append(qt_symbian_appFontNameWithoutMarker(appFontName)); + if (!qt_symbian_fontNameHasAppFontMarker(appFontName) + || !registerScreenDeviceFont(fontOnServerIndex, dbExtras)) + dbExtras->removeAppFontData(fnt); + } else { + if (fnt->screenDeviceFontFileId > 0) + S60->screenDevice()->RemoveFile(fnt->screenDeviceFontFileId); // May still have the file open! + QFile::remove(fnt->temporaryFileName); + *fnt = QFontDatabasePrivate::ApplicationFont(); + } + lock.relock(); +} + +bool QFontDatabase::removeApplicationFont(int handle) +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (!db || handle < 0 || handle >= db->applicationFonts.count()) + return false; + QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast(db->symbianExtras); + if (!dbExtras) + return false; + + QFontDatabasePrivate::ApplicationFont *fnt = &db->applicationFonts[handle]; + if (fnt->families.isEmpty()) + return true; // Nothing to remove. Return peacefully. + + // Must remove all font engines with their CFonts, first + QFontCache::instance()->clear(); + db->free(); + dbExtras->removeAppFontData(fnt); + + db->invalidate(); // This will just emit 'fontDatabaseChanged()' + return true; +} + +bool QFontDatabase::removeAllApplicationFonts() +{ + QMutexLocker locker(fontDatabaseMutex()); + + const int applicationFontsCount = privateDb()->applicationFonts.count(); + for (int i = 0; i < applicationFontsCount; ++i) + if (!removeApplicationFont(i)) + return false; + return true; +} + +bool QFontDatabase::supportsThreadedFontRendering() +{ + return false; +} + +static +QFontDef cleanedFontDef(const QFontDef &req) +{ + QFontDef result = req; + if (result.pixelSize <= 0) { + result.pixelSize = QFontEngineS60::pointsToPixels(qMax(qreal(1.0), result.pointSize)); + result.pointSize = 0; + } + return result; +} + +QFontEngine *QFontDatabase::findFont(int script, const QFontPrivate *d, const QFontDef &req) +{ + const QFontCache::Key key(cleanedFontDef(req), script); + + if (!privateDb()->count) + initializeDb(); + + QFontEngine *fe = QFontCache::instance()->findEngine(key); + if (!fe) { + // Making sure that fe->fontDef.family will be an existing font. + initializeDb(); + QFontDatabasePrivate *db = privateDb(); + QtFontDesc desc; + QList blacklistedFamilies; + match(script, key.def, key.def.family, QString(), -1, &desc, blacklistedFamilies); + if (!desc.family) // falling back to application font + desc.family = db->family(QApplication::font().defaultFamily()); + Q_ASSERT(desc.family); + + // Making sure that desc.family supports the requested script + QtFontDesc mappedDesc; + bool supportsScript = false; + do { + match(script, req, QString(), QString(), -1, &mappedDesc, blacklistedFamilies); + if (mappedDesc.family == desc.family) { + supportsScript = true; + break; + } + blacklistedFamilies.append(mappedDesc.familyIndex); + } while (mappedDesc.family); + if (!supportsScript) { + blacklistedFamilies.clear(); + match(script, req, QString(), QString(), -1, &mappedDesc, blacklistedFamilies); + if (mappedDesc.family) + desc = mappedDesc; + } + + const QString fontFamily = desc.family->name; + QFontDef request = req; + request.family = fontFamily; +#ifdef QT_NO_FREETYPE + const QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast(db->symbianExtras); + const QSymbianTypeFaceExtras *typeFaceExtras = + dbExtras->extras(fontFamily, request.weight > QFont::Normal, request.style != QFont::StyleNormal); + + // We need a valid pixelSize, e.g. for lineThickness() + if (request.pixelSize < 0) + request.pixelSize = request.pointSize * d->dpi / 72; + + fe = new QFontEngineS60(request, typeFaceExtras); +#else // QT_NO_FREETYPE + Q_UNUSED(d) + QFontEngine::FaceId faceId; + const QtFontFamily * const reqQtFontFamily = db->family(fontFamily); + faceId.filename = reqQtFontFamily->fontFilename; + faceId.index = reqQtFontFamily->fontFileIndex; + + QFontEngineFTS60 *fte = new QFontEngineFTS60(cleanedFontDef(request)); + if (fte->init(faceId, true, QFontEngineFT::Format_A8)) + fe = fte; + else + delete fte; +#endif // QT_NO_FREETYPE + + Q_ASSERT(fe); + if (script == QUnicodeTables::Common + && !(req.styleStrategy & QFont::NoFontMerging) + && !fe->symbol) { + + QStringList commonFonts; + for (int ws = 1; ws < QFontDatabase::WritingSystemsCount; ++ws) { + if (scriptForWritingSystem[ws] != script) + continue; + for (int i = 0; i < db->count; ++i) { + if (db->families[i]->writingSystems[ws] & QtFontFamily::Supported) + commonFonts.append(db->families[i]->name); + } + } + + // Hack: Prioritize .ccc fonts + const QString niceEastAsianFont(QLatin1String("Sans MT 936_S60")); + if (commonFonts.removeAll(niceEastAsianFont) > 0) + commonFonts.prepend(niceEastAsianFont); + + fe = new QFontEngineMultiS60(fe, script, commonFonts); + } + } + fe->ref.ref(); + QFontCache::instance()->insertEngine(key, fe); + return fe; +} + +void QFontDatabase::load(const QFontPrivate *d, int script) +{ + QFontEngine *fe = 0; + QFontDef req = d->request; + + if (!d->engineData) { + const QFontCache::Key key(cleanedFontDef(req), script); + getEngineData(d, key); + } + + // the cached engineData could have already loaded the engine we want + if (d->engineData->engines[script]) + fe = d->engineData->engines[script]; + + if (!fe) { + if (qt_enable_test_font && req.family == QLatin1String("__Qt__Box__Engine__")) { + fe = new QTestFontEngine(req.pixelSize); + fe->fontDef = req; + } else { + fe = findFont(script, d, req); + } + d->engineData->engines[script] = fe; + } +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontdatabase_win.cpp b/src/gui/text/qfontdatabase_win.cpp new file mode 100644 index 0000000000..05b7509bf6 --- /dev/null +++ b/src/gui/text/qfontdatabase_win.cpp @@ -0,0 +1,1348 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qt_windows.h" +#include +#include +#include "qfont_p.h" +#include "qfontengine_p.h" +#include "qpaintdevice.h" +#include +#include "qabstractfileengine.h" +#include "qendian.h" + +#if !defined(QT_NO_DIRECTWRITE) +# include "qsettings.h" +# include "qfontenginedirectwrite_p.h" +#endif + +#ifdef Q_OS_WINCE +# include +#endif + +QT_BEGIN_NAMESPACE + +extern HDC shared_dc(); // common dc for all fonts + +#ifdef MAKE_TAG +#undef MAKE_TAG +#endif +// GetFontData expects the tags in little endian ;( +#define MAKE_TAG(ch1, ch2, ch3, ch4) (\ + (((quint32)(ch4)) << 24) | \ + (((quint32)(ch3)) << 16) | \ + (((quint32)(ch2)) << 8) | \ + ((quint32)(ch1)) \ + ) + +static HFONT stock_sysfont = 0; + +static bool localizedName(const QString &name) +{ + const QChar *c = name.unicode(); + for(int i = 0; i < name.length(); ++i) { + if(c[i].unicode() >= 0x100) + return true; + } + return false; +} + +static inline quint16 getUShort(const unsigned char *p) +{ + quint16 val; + val = *p++ << 8; + val |= *p; + + return val; +} + +static QString getEnglishName(const uchar *table, quint32 bytes) +{ + QString i18n_name; + enum { + NameRecordSize = 12, + FamilyId = 1, + MS_LangIdEnglish = 0x009 + }; + + // get the name table + quint16 count; + quint16 string_offset; + const unsigned char *names; + + int microsoft_id = -1; + int apple_id = -1; + int unicode_id = -1; + + if(getUShort(table) != 0) + goto error; + + count = getUShort(table+2); + string_offset = getUShort(table+4); + names = table + 6; + + if(string_offset >= bytes || 6 + count*NameRecordSize > string_offset) + goto error; + + for(int i = 0; i < count; ++i) { + // search for the correct name entry + + quint16 platform_id = getUShort(names + i*NameRecordSize); + quint16 encoding_id = getUShort(names + 2 + i*NameRecordSize); + quint16 language_id = getUShort(names + 4 + i*NameRecordSize); + quint16 name_id = getUShort(names + 6 + i*NameRecordSize); + + if(name_id != FamilyId) + continue; + + enum { + PlatformId_Unicode = 0, + PlatformId_Apple = 1, + PlatformId_Microsoft = 3 + }; + + quint16 length = getUShort(names + 8 + i*NameRecordSize); + quint16 offset = getUShort(names + 10 + i*NameRecordSize); + if(DWORD(string_offset + offset + length) >= bytes) + continue; + + if ((platform_id == PlatformId_Microsoft + && (encoding_id == 0 || encoding_id == 1)) + && (language_id & 0x3ff) == MS_LangIdEnglish + && microsoft_id == -1) + microsoft_id = i; + // not sure if encoding id 4 for Unicode is utf16 or ucs4... + else if(platform_id == PlatformId_Unicode && encoding_id < 4 && unicode_id == -1) + unicode_id = i; + else if(platform_id == PlatformId_Apple && encoding_id == 0 && language_id == 0) + apple_id = i; + } + { + bool unicode = false; + int id = -1; + if(microsoft_id != -1) { + id = microsoft_id; + unicode = true; + } else if(apple_id != -1) { + id = apple_id; + unicode = false; + } else if (unicode_id != -1) { + id = unicode_id; + unicode = true; + } + if(id != -1) { + quint16 length = getUShort(names + 8 + id*NameRecordSize); + quint16 offset = getUShort(names + 10 + id*NameRecordSize); + if(unicode) { + // utf16 + + length /= 2; + i18n_name.resize(length); + QChar *uc = (QChar *) i18n_name.unicode(); + const unsigned char *string = table + string_offset + offset; + for(int i = 0; i < length; ++i) + uc[i] = getUShort(string + 2*i); + } else { + // Apple Roman + + i18n_name.resize(length); + QChar *uc = (QChar *) i18n_name.unicode(); + const unsigned char *string = table + string_offset + offset; + for(int i = 0; i < length; ++i) + uc[i] = QLatin1Char(string[i]); + } + } + } + error: + //qDebug("got i18n name of '%s' for font '%s'", i18n_name.latin1(), familyName.toLocal8Bit().data()); + return i18n_name; +} + +static QString getEnglishName(const QString &familyName) +{ + QString i18n_name; + + HDC hdc = GetDC( 0 ); + LOGFONT lf; + memset(&lf, 0, sizeof(LOGFONT)); + memcpy(lf.lfFaceName, familyName.utf16(), qMin(LF_FACESIZE, familyName.length()) * sizeof(wchar_t)); + lf.lfCharSet = DEFAULT_CHARSET; + HFONT hfont = CreateFontIndirect(&lf); + + if(!hfont) { + ReleaseDC(0, hdc); + return QString(); + } + + HGDIOBJ oldobj = SelectObject( hdc, hfont ); + + const DWORD name_tag = MAKE_TAG( 'n', 'a', 'm', 'e' ); + + // get the name table + unsigned char *table = 0; + + DWORD bytes = GetFontData( hdc, name_tag, 0, 0, 0 ); + if ( bytes == GDI_ERROR ) { + // ### Unused variable + /* int err = GetLastError(); */ + goto error; + } + + table = new unsigned char[bytes]; + GetFontData(hdc, name_tag, 0, table, bytes); + if ( bytes == GDI_ERROR ) + goto error; + + i18n_name = getEnglishName(table, bytes); +error: + delete [] table; + SelectObject( hdc, oldobj ); + DeleteObject( hfont ); + ReleaseDC( 0, hdc ); + + //qDebug("got i18n name of '%s' for font '%s'", i18n_name.latin1(), familyName.toLocal8Bit().data()); + return i18n_name; +} + +extern QFont::Weight weightFromInteger(int weight); // qfontdatabase.cpp + +static +void addFontToDatabase(QString familyName, const QString &scriptName, + TEXTMETRIC *textmetric, + const FONTSIGNATURE *signature, + int type) +{ + const int script = -1; + const QString foundryName; + Q_UNUSED(script); + + bool italic = false; + int weight; + bool fixed; + bool ttf; + bool scalable; + int size; + +// QString escript = QString::fromWCharArray(f->elfScript); +// qDebug("script=%s", escript.latin1()); + + NEWTEXTMETRIC *tm = (NEWTEXTMETRIC *)textmetric; + fixed = !(tm->tmPitchAndFamily & TMPF_FIXED_PITCH); + ttf = (tm->tmPitchAndFamily & TMPF_TRUETYPE); + scalable = tm->tmPitchAndFamily & (TMPF_VECTOR|TMPF_TRUETYPE); + size = scalable ? SMOOTH_SCALABLE : tm->tmHeight; + italic = tm->tmItalic; + weight = tm->tmWeight; + + // the "@family" fonts are just the same as "family". Ignore them. + if (familyName[0] != QLatin1Char('@') && !familyName.startsWith(QLatin1String("WST_"))) { + QtFontStyle::Key styleKey; + styleKey.style = italic ? QFont::StyleItalic : QFont::StyleNormal; + styleKey.weight = weightFromInteger(weight); + + QtFontFamily *family = privateDb()->family(familyName, true); + + if(ttf && localizedName(familyName) && family->english_name.isEmpty()) + family->english_name = getEnglishName(familyName); + + QtFontFoundry *foundry = family->foundry(foundryName, true); + QtFontStyle *style = foundry->style(styleKey, true); + style->smoothScalable = scalable; + style->pixelSize( size, TRUE); + + // add fonts windows can generate for us: + if (styleKey.weight <= QFont::DemiBold) { + QtFontStyle::Key key(styleKey); + key.weight = QFont::Bold; + QtFontStyle *style = foundry->style(key, true); + style->smoothScalable = scalable; + style->pixelSize( size, TRUE); + } + if (styleKey.style != QFont::StyleItalic) { + QtFontStyle::Key key(styleKey); + key.style = QFont::StyleItalic; + QtFontStyle *style = foundry->style(key, true); + style->smoothScalable = scalable; + style->pixelSize( size, TRUE); + } + if (styleKey.weight <= QFont::DemiBold && styleKey.style != QFont::StyleItalic) { + QtFontStyle::Key key(styleKey); + key.weight = QFont::Bold; + key.style = QFont::StyleItalic; + QtFontStyle *style = foundry->style(key, true); + style->smoothScalable = scalable; + style->pixelSize( size, TRUE); + } + + family->fixedPitch = fixed; + + if (!family->writingSystemCheck && type & TRUETYPE_FONTTYPE) { + quint32 unicodeRange[4] = { + signature->fsUsb[0], signature->fsUsb[1], + signature->fsUsb[2], signature->fsUsb[3] + }; +#ifdef Q_WS_WINCE + if (signature->fsUsb[0] == 0) { + // If the unicode ranges bit mask is zero then + // EnumFontFamiliesEx failed to determine it properly. + // In this case we just pretend that the font supports all languages. + unicodeRange[0] = 0xbfffffff; // second most significant bit must be zero + unicodeRange[1] = 0xffffffff; + unicodeRange[2] = 0xffffffff; + unicodeRange[3] = 0xffffffff; + } +#endif + quint32 codePageRange[2] = { + signature->fsCsb[0], signature->fsCsb[1] + }; + QList systems = qt_determine_writing_systems_from_truetype_bits(unicodeRange, codePageRange); + + for (int i = 0; i < systems.count(); ++i) { + QFontDatabase::WritingSystem writingSystem = systems.at(i); + + // ### Hack to work around problem with Thai text on Windows 7. Segoe UI contains + // the symbol for Baht, and Windows thus reports that it supports the Thai script. + // Since it's the default UI font on this platform, most widgets will be unable to + // display Thai text by default. As a temporary work around, we special case Segoe UI + // and remove the Thai script from its list of supported writing systems. + if (writingSystem != QFontDatabase::Thai || familyName != QLatin1String("Segoe UI")) + family->writingSystems[writingSystem] = QtFontFamily::Supported; + } + } else if (!family->writingSystemCheck) { + //qDebug("family='%s' script=%s", family->name.latin1(), script.latin1()); + if (scriptName == QLatin1String("Western") + || scriptName == QLatin1String("Baltic") + || scriptName == QLatin1String("Central European") + || scriptName == QLatin1String("Turkish") + || scriptName == QLatin1String("Vietnamese")) + family->writingSystems[QFontDatabase::Latin] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Thai")) + family->writingSystems[QFontDatabase::Thai] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Symbol") + || scriptName == QLatin1String("Other")) + family->writingSystems[QFontDatabase::Symbol] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("OEM/Dos")) + family->writingSystems[QFontDatabase::Latin] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("CHINESE_GB2312")) + family->writingSystems[QFontDatabase::SimplifiedChinese] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("CHINESE_BIG5")) + family->writingSystems[QFontDatabase::TraditionalChinese] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Cyrillic")) + family->writingSystems[QFontDatabase::Cyrillic] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Hangul")) + family->writingSystems[QFontDatabase::Korean] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Hebrew")) + family->writingSystems[QFontDatabase::Hebrew] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Greek")) + family->writingSystems[QFontDatabase::Greek] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Japanese")) + family->writingSystems[QFontDatabase::Japanese] = QtFontFamily::Supported; + else if (scriptName == QLatin1String("Arabic")) + family->writingSystems[QFontDatabase::Arabic] = QtFontFamily::Supported; + } + } +} + +static +int CALLBACK +storeFont(ENUMLOGFONTEX* f, NEWTEXTMETRICEX *textmetric, int type, LPARAM /*p*/) +{ + QString familyName = QString::fromWCharArray(f->elfLogFont.lfFaceName); + QString script = QString::fromWCharArray(f->elfScript); + + FONTSIGNATURE signature = textmetric->ntmFontSig; + + // NEWTEXTMETRICEX is a NEWTEXTMETRIC, which according to the documentation is + // identical to a TEXTMETRIC except for the last four members, which we don't use + // anyway + addFontToDatabase(familyName, script, (TEXTMETRIC *)textmetric, &signature, type); + // keep on enumerating + return 1; +} + +static +void populate_database(const QString& fam) +{ + QFontDatabasePrivate *d = privateDb(); + if (!d) + return; + + QtFontFamily *family = 0; + if(!fam.isEmpty()) { + family = d->family(fam); + if(family && family->loaded) + return; + } else if (d->count) { + return; + } + + HDC dummy = GetDC(0); + + LOGFONT lf; + lf.lfCharSet = DEFAULT_CHARSET; + if (fam.isNull()) { + lf.lfFaceName[0] = 0; + } else { + memcpy(lf.lfFaceName, fam.utf16(), sizeof(wchar_t) * qMin(fam.length() + 1, 32)); // 32 = Windows hard-coded + } + lf.lfPitchAndFamily = 0; + + EnumFontFamiliesEx(dummy, &lf, + (FONTENUMPROC)storeFont, (LPARAM)privateDb(), 0); + + ReleaseDC(0, dummy); + + for (int i = 0; i < d->applicationFonts.count(); ++i) { + QFontDatabasePrivate::ApplicationFont fnt = d->applicationFonts.at(i); + if (!fnt.memoryFont) + continue; + for (int j = 0; j < fnt.families.count(); ++j) { + const QString familyName = fnt.families.at(j); + HDC hdc = GetDC(0); + LOGFONT lf; + memset(&lf, 0, sizeof(LOGFONT)); + memcpy(lf.lfFaceName, familyName.utf16(), sizeof(wchar_t) * qMin(LF_FACESIZE, familyName.size())); + lf.lfCharSet = DEFAULT_CHARSET; + HFONT hfont = CreateFontIndirect(&lf); + HGDIOBJ oldobj = SelectObject(hdc, hfont); + + TEXTMETRIC textMetrics; + GetTextMetrics(hdc, &textMetrics); + + addFontToDatabase(familyName, QString(), + &textMetrics, + &fnt.signatures.at(j), + TRUETYPE_FONTTYPE); + + SelectObject(hdc, oldobj); + DeleteObject(hfont); + ReleaseDC(0, hdc); + } + } + + if(!fam.isEmpty()) { + family = d->family(fam); + if(family) { + if(!family->writingSystemCheck) { + } + family->loaded = true; + } + } +} + +static void initializeDb() +{ + QFontDatabasePrivate *db = privateDb(); + if (!db || db->count) + return; + + populate_database(QString()); + +#ifdef QFONTDATABASE_DEBUG + // print the database + for (int f = 0; f < db->count; f++) { + QtFontFamily *family = db->families[f]; + qDebug(" %s: %p", qPrintable(family->name), family); + populate_database(family->name); + +#if 0 + qDebug(" scripts supported:"); + for (int i = 0; i < QUnicodeTables::ScriptCount; i++) + if(family->writingSystems[i] & QtFontFamily::Supported) + qDebug(" %d", i); + for (int fd = 0; fd < family->count; fd++) { + QtFontFoundry *foundry = family->foundries[fd]; + qDebug(" %s", foundry->name.latin1()); + for (int s = 0; s < foundry->count; s++) { + QtFontStyle *style = foundry->styles[s]; + qDebug(" style: style=%d weight=%d smooth=%d", style->key.style, + style->key.weight, style->smoothScalable ); + if(!style->smoothScalable) { + for(int i = 0; i < style->count; ++i) { + qDebug(" %d", style->pixelSizes[i].pixelSize); + } + } + } + } +#endif + } +#endif // QFONTDATABASE_DEBUG + +} + +static inline void load(const QString &family = QString(), int = -1) +{ + populate_database(family); +} + + + + + +// -------------------------------------------------------------------------------------- +// font loader +// -------------------------------------------------------------------------------------- + + + +static void initFontInfo(QFontEngineWin *fe, const QFontDef &request, HDC fontHdc, int dpi) +{ + fe->fontDef = request; // most settings are equal + + HDC dc = ((request.styleStrategy & QFont::PreferDevice) && fontHdc) ? fontHdc : shared_dc(); + SelectObject(dc, fe->hfont); + wchar_t n[64]; + GetTextFace(dc, 64, n); + fe->fontDef.family = QString::fromWCharArray(n); + fe->fontDef.fixedPitch = !(fe->tm.tmPitchAndFamily & TMPF_FIXED_PITCH); + if (fe->fontDef.pointSize < 0) { + fe->fontDef.pointSize = fe->fontDef.pixelSize * 72. / dpi; + } else if (fe->fontDef.pixelSize == -1) { + fe->fontDef.pixelSize = qRound(fe->fontDef.pointSize * dpi / 72.); + } +} + +#if !defined(QT_NO_DIRECTWRITE) +static void initFontInfo(QFontEngineDirectWrite *fe, const QFontDef &request, + int dpi, IDWriteFont *font) +{ + fe->fontDef = request; + + IDWriteFontFamily *fontFamily = NULL; + HRESULT hr = font->GetFontFamily(&fontFamily); + + IDWriteLocalizedStrings *familyNames = NULL; + if (SUCCEEDED(hr)) + hr = fontFamily->GetFamilyNames(&familyNames); + + UINT32 index = 0; + BOOL exists = false; + + wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; + + if (SUCCEEDED(hr)) { + int defaultLocaleSuccess = GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH); + + if (defaultLocaleSuccess) + hr = familyNames->FindLocaleName(localeName, &index, &exists); + + if (SUCCEEDED(hr) && !exists) + hr = familyNames->FindLocaleName(L"en-us", &index, &exists); + } + + if (!exists) + index = 0; + + UINT32 length = 0; + if (SUCCEEDED(hr)) + hr = familyNames->GetStringLength(index, &length); + + wchar_t *name = new (std::nothrow) wchar_t[length+1]; + if (name == NULL) + hr = E_OUTOFMEMORY; + + // Get the family name. + if (SUCCEEDED(hr)) + hr = familyNames->GetString(index, name, length + 1); + + if (SUCCEEDED(hr)) + fe->fontDef.family = QString::fromWCharArray(name); + + delete[] name; + if (familyNames != NULL) + familyNames->Release(); + + if (FAILED(hr)) + qErrnoWarning(hr, "initFontInfo: Failed to get family name"); + + if (fe->fontDef.pointSize < 0) + fe->fontDef.pointSize = fe->fontDef.pixelSize * 72. / dpi; + else if (fe->fontDef.pixelSize == -1) + fe->fontDef.pixelSize = qRound(fe->fontDef.pointSize * dpi / 72.); +} +#endif + +static const char *other_tryFonts[] = { + "Arial", + "MS UI Gothic", + "Gulim", + "SimSun", + "PMingLiU", + "Arial Unicode MS", + 0 +}; + +static const char *jp_tryFonts [] = { + "MS UI Gothic", + "Arial", + "Gulim", + "SimSun", + "PMingLiU", + "Arial Unicode MS", + 0 +}; + +static const char *ch_CN_tryFonts [] = { + "SimSun", + "Arial", + "PMingLiU", + "Gulim", + "MS UI Gothic", + "Arial Unicode MS", + 0 +}; + +static const char *ch_TW_tryFonts [] = { + "PMingLiU", + "Arial", + "SimSun", + "Gulim", + "MS UI Gothic", + "Arial Unicode MS", + 0 +}; + +static const char *kr_tryFonts[] = { + "Gulim", + "Arial", + "PMingLiU", + "SimSun", + "MS UI Gothic", + "Arial Unicode MS", + 0 +}; + +static const char **tryFonts = 0; + +#if !defined(QT_NO_DIRECTWRITE) +static QString fontNameSubstitute(const QString &familyName) +{ + QLatin1String key("HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\" + "FontSubstitutes"); + return QSettings(key, QSettings::NativeFormat).value(familyName, familyName).toString(); +} +#endif + +static inline HFONT systemFont() +{ + if (stock_sysfont == 0) + stock_sysfont = (HFONT)GetStockObject(SYSTEM_FONT); + return stock_sysfont; +} + +#if !defined(DEFAULT_GUI_FONT) +#define DEFAULT_GUI_FONT 17 +#endif + +static QFontEngine *loadEngine(int script, const QFontDef &request, + HDC fontHdc, int dpi, bool rawMode, + const QtFontDesc *desc, + const QStringList &family_list) +{ + LOGFONT lf; + memset(&lf, 0, sizeof(LOGFONT)); + + bool useDevice = (request.styleStrategy & QFont::PreferDevice) && fontHdc; + + HDC hdc = shared_dc(); + QString font_name = desc != 0 ? desc->family->name : request.family; + + if (useDevice) { + hdc = fontHdc; + font_name = request.family; + } + + bool stockFont = false; + bool preferClearTypeAA = false; + + HFONT hfont = 0; + + +#if !defined(QT_NO_DIRECTWRITE) + bool useDirectWrite = (request.hintingPreference == QFont::PreferNoHinting) + || (request.hintingPreference == QFont::PreferVerticalHinting); + IDWriteFont *directWriteFont = 0; +#else + bool useDirectWrite = false; +#endif + + if (rawMode) { // will choose a stock font + int f, deffnt = SYSTEM_FONT; + QString fam = desc != 0 ? desc->family->name.toLower() : request.family.toLower(); + if (fam == QLatin1String("default")) + f = deffnt; + else if (fam == QLatin1String("system")) + f = SYSTEM_FONT; +#ifndef Q_WS_WINCE + else if (fam == QLatin1String("system_fixed")) + f = SYSTEM_FIXED_FONT; + else if (fam == QLatin1String("ansi_fixed")) + f = ANSI_FIXED_FONT; + else if (fam == QLatin1String("ansi_var")) + f = ANSI_VAR_FONT; + else if (fam == QLatin1String("device_default")) + f = DEVICE_DEFAULT_FONT; + else if (fam == QLatin1String("oem_fixed")) + f = OEM_FIXED_FONT; +#endif + else if (fam[0] == QLatin1Char('#')) + f = fam.right(fam.length()-1).toInt(); + else + f = deffnt; + hfont = (HFONT)GetStockObject(f); + if (!hfont) { + qErrnoWarning("QFontEngine::loadEngine: GetStockObject failed"); + hfont = systemFont(); + } + stockFont = true; + } else { + + int hint = FF_DONTCARE; + switch (request.styleHint) { + case QFont::Helvetica: + hint = FF_SWISS; + break; + case QFont::Times: + hint = FF_ROMAN; + break; + case QFont::Courier: + hint = FF_MODERN; + break; + case QFont::OldEnglish: + hint = FF_DECORATIVE; + break; + case QFont::System: + hint = FF_MODERN; + break; + default: + break; + } + + lf.lfHeight = -qRound(request.pixelSize); + lf.lfWidth = 0; + lf.lfEscapement = 0; + lf.lfOrientation = 0; + if (desc == 0 || desc->style->key.weight == 50) + lf.lfWeight = FW_DONTCARE; + else + lf.lfWeight = (desc->style->key.weight*900)/99; + lf.lfItalic = (desc != 0 && desc->style->key.style != QFont::StyleNormal); + lf.lfCharSet = DEFAULT_CHARSET; + + int strat = OUT_DEFAULT_PRECIS; + if (request.styleStrategy & QFont::PreferBitmap) { + strat = OUT_RASTER_PRECIS; +#ifndef Q_WS_WINCE + } else if (request.styleStrategy & QFont::PreferDevice) { + strat = OUT_DEVICE_PRECIS; + } else if (request.styleStrategy & QFont::PreferOutline) { + strat = OUT_OUTLINE_PRECIS; + } else if (request.styleStrategy & QFont::ForceOutline) { + strat = OUT_TT_ONLY_PRECIS; +#endif + } + + lf.lfOutPrecision = strat; + + int qual = DEFAULT_QUALITY; + + if (request.styleStrategy & QFont::PreferMatch) + qual = DRAFT_QUALITY; +#ifndef Q_WS_WINCE + else if (request.styleStrategy & QFont::PreferQuality) + qual = PROOF_QUALITY; +#endif + + if (request.styleStrategy & QFont::PreferAntialias) { + if (QSysInfo::WindowsVersion >= QSysInfo::WV_XP) { + qual = CLEARTYPE_QUALITY; + preferClearTypeAA = true; + } else { + qual = ANTIALIASED_QUALITY; + } + } else if (request.styleStrategy & QFont::NoAntialias) { + qual = NONANTIALIASED_QUALITY; + } + + lf.lfQuality = qual; + + lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf.lfPitchAndFamily = DEFAULT_PITCH | hint; + + QString fam = font_name; + + if(fam.isEmpty()) + fam = QLatin1String("MS Sans Serif"); + + if ((fam == QLatin1String("MS Sans Serif")) + && (request.style == QFont::StyleItalic || (-lf.lfHeight > 18 && -lf.lfHeight != 24))) { + fam = QLatin1String("Arial"); // MS Sans Serif has bearing problems in italic, and does not scale + } + if (fam == QLatin1String("Courier") && !(request.styleStrategy & QFont::PreferBitmap)) + fam = QLatin1String("Courier New"); + + memcpy(lf.lfFaceName, fam.utf16(), sizeof(wchar_t) * qMin(fam.length() + 1, 32)); // 32 = Windows hard-coded + + hfont = CreateFontIndirect(&lf); + if (!hfont) + qErrnoWarning("QFontEngine::loadEngine: CreateFontIndirect failed"); + + stockFont = (hfont == 0); + bool ttf = false; + int avWidth = 0; + BOOL res; + HGDIOBJ oldObj = SelectObject(hdc, hfont); + + TEXTMETRIC tm; + res = GetTextMetrics(hdc, &tm); + avWidth = tm.tmAveCharWidth; + ttf = tm.tmPitchAndFamily & TMPF_TRUETYPE; + SelectObject(hdc, oldObj); + + if (!ttf || !useDirectWrite) { + useDirectWrite = false; + + if (hfont && (!ttf || request.stretch != 100)) { + DeleteObject(hfont); + if (!res) + qErrnoWarning("QFontEngine::loadEngine: GetTextMetrics failed"); + lf.lfWidth = avWidth * request.stretch/100; + hfont = CreateFontIndirect(&lf); + if (!hfont) + qErrnoWarning("QFontEngine::loadEngine: CreateFontIndirect with stretch failed"); + } + +#ifndef Q_WS_WINCE + if (hfont == 0) { + hfont = (HFONT)GetStockObject(ANSI_VAR_FONT); + stockFont = true; + } +#else + if (hfont == 0) { + hfont = (HFONT)GetStockObject(SYSTEM_FONT); + stockFont = true; + } +#endif + + } + +#if !defined(QT_NO_DIRECTWRITE) + else { + // Default to false for DirectWrite (and re-enable once/if everything + // turns out okay) + useDirectWrite = false; + + QFontDatabasePrivate *db = privateDb(); + if (db->directWriteFactory == 0) { + HRESULT hr = DWriteCreateFactory( + DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory), + reinterpret_cast(&db->directWriteFactory) + ); + if (FAILED(hr)) { + qErrnoWarning("QFontEngine::loadEngine: DWriteCreateFactory failed"); + } else { + hr = db->directWriteFactory->GetGdiInterop(&db->directWriteGdiInterop); + if (FAILED(hr)) + qErrnoWarning("QFontEngine::loadEngine: GetGdiInterop failed"); + } + } + + if (db->directWriteGdiInterop != 0) { + QString nameSubstitute = fontNameSubstitute(QString::fromWCharArray(lf.lfFaceName)); + memcpy(lf.lfFaceName, nameSubstitute.utf16(), + sizeof(wchar_t) * qMin(nameSubstitute.length() + 1, LF_FACESIZE)); + + HRESULT hr = db->directWriteGdiInterop->CreateFontFromLOGFONT( + &lf, + &directWriteFont); + if (FAILED(hr)) { +#ifndef QT_NO_DEBUG + qErrnoWarning("QFontEngine::loadEngine: CreateFontFromLOGFONT failed " + "for %ls (0x%lx)", + lf.lfFaceName, hr); +#endif + } else { + DeleteObject(hfont); + useDirectWrite = true; + } + } + } +#endif + + } + + QFontEngine *fe = 0; + if (!useDirectWrite) { + QFontEngineWin *few = new QFontEngineWin(font_name, hfont, stockFont, lf); + if (preferClearTypeAA) + few->glyphFormat = QFontEngineGlyphCache::Raster_RGBMask; + + // Also check for OpenType tables when using complex scripts + // ### TODO: This only works for scripts that require OpenType. More generally + // for scripts that do not require OpenType we should just look at the list of + // supported writing systems in the font's OS/2 table. + if (scriptRequiresOpenType(script)) { + HB_Face hbFace = few->harfbuzzFace(); + if (!hbFace || !hbFace->supported_scripts[script]) { + FM_DEBUG(" OpenType support missing for script\n"); + delete few; + return 0; + } + } + + initFontInfo(few, request, fontHdc, dpi); + fe = few; + } + +#if !defined(QT_NO_DIRECTWRITE) + else { + QFontDatabasePrivate *db = privateDb(); + + IDWriteFontFace *directWriteFontFace = NULL; + HRESULT hr = directWriteFont->CreateFontFace(&directWriteFontFace); + if (SUCCEEDED(hr)) { + QFontEngineDirectWrite *fedw = new QFontEngineDirectWrite(db->directWriteFactory, + directWriteFontFace, + request.pixelSize); + + initFontInfo(fedw, request, dpi, directWriteFont); + + fe = fedw; + } else { + qErrnoWarning(hr, "QFontEngine::loadEngine: CreateFontFace failed"); + } + } + + if (directWriteFont != 0) + directWriteFont->Release(); +#endif + + if(script == QUnicodeTables::Common + && !(request.styleStrategy & QFont::NoFontMerging) + && desc != 0 + && !(desc->family->writingSystems[QFontDatabase::Symbol] & QtFontFamily::Supported)) { + if(!tryFonts) { + LANGID lid = GetUserDefaultLangID(); + switch( lid&0xff ) { + case LANG_CHINESE: // Chinese (Taiwan) + if ( lid == 0x0804 ) // Taiwan + tryFonts = ch_TW_tryFonts; + else + tryFonts = ch_CN_tryFonts; + break; + case LANG_JAPANESE: + tryFonts = jp_tryFonts; + break; + case LANG_KOREAN: + tryFonts = kr_tryFonts; + break; + default: + tryFonts = other_tryFonts; + break; + } + } + QStringList fm = QFontDatabase().families(); + QStringList list = family_list; + const char **tf = tryFonts; + while(tf && *tf) { + if(fm.contains(QLatin1String(*tf))) + list << QLatin1String(*tf); + ++tf; + } + QFontEngine *mfe = new QFontEngineMultiWin(fe, list); + mfe->fontDef = fe->fontDef; + fe = mfe; + } + return fe; +} + +QFontEngine *qt_load_font_engine_win(const QFontDef &request) +{ + // From qfont.cpp + extern int qt_defaultDpi(); + + QFontCache::Key key(request, QUnicodeTables::Common); + QFontEngine *fe = QFontCache::instance()->findEngine(key); + if (fe != 0) + return fe; + else + return loadEngine(QUnicodeTables::Common, request, 0, qt_defaultDpi(), false, 0, + QStringList()); +} + +const char *styleHint(const QFontDef &request) +{ + const char *stylehint = 0; + switch (request.styleHint) { + case QFont::SansSerif: + stylehint = "Arial"; + break; + case QFont::Serif: + stylehint = "Times New Roman"; + break; + case QFont::TypeWriter: + stylehint = "Courier New"; + break; + default: + if (request.fixedPitch) + stylehint = "Courier New"; + break; + } + return stylehint; +} + +static QFontEngine *loadWin(const QFontPrivate *d, int script, const QFontDef &req) +{ + // list of families to try + QStringList family_list = familyList(req); + + const char *stylehint = styleHint(d->request); + if (stylehint) + family_list << QLatin1String(stylehint); + + // append the default fallback font for the specified script + // family_list << ... ; ########### + + // add the default family + QString defaultFamily = QApplication::font().family(); + if (! family_list.contains(defaultFamily)) + family_list << defaultFamily; + + // add QFont::defaultFamily() to the list, for compatibility with + // previous versions + family_list << QApplication::font().defaultFamily(); + + // null family means find the first font matching the specified script + family_list << QString(); + + QtFontDesc desc; + QFontEngine *fe = 0; + QList blacklistedFamilies; + + while (!fe) { + for (int i = 0; i < family_list.size(); ++i) { + QString family, foundry; + parseFontName(family_list.at(i), foundry, family); + FM_DEBUG("loadWin: >>>>>>>>>>>>>>trying to match '%s'", family.toLatin1().data()); + QT_PREPEND_NAMESPACE(match)(script, req, family, foundry, -1, &desc, blacklistedFamilies); + if (desc.family) + break; + } + if (!desc.family) + break; + fe = loadEngine(script, req, d->hdc, d->dpi, d->rawMode, &desc, family_list); + if (!fe) + blacklistedFamilies.append(desc.familyIndex); + } + return fe; +} + +void QFontDatabase::load(const QFontPrivate *d, int script) +{ + // sanity checks + if (!qApp) + qWarning("QFontDatabase::load: Must construct QApplication first"); + Q_ASSERT(script >= 0 && script < QUnicodeTables::ScriptCount); + + // normalize the request to get better caching + QFontDef req = d->request; + if (req.pixelSize <= 0) + req.pixelSize = floor((100.0 * req.pointSize * d->dpi) / 72. + 0.5) / 100; + if (req.pixelSize < 1) + req.pixelSize = 1; + if (req.weight == 0) + req.weight = QFont::Normal; + if (req.stretch == 0) + req.stretch = 100; + + QFontCache::Key key(req, d->rawMode ? QUnicodeTables::Common : script, d->screen); + if (!d->engineData) + getEngineData(d, key); + + // the cached engineData could have already loaded the engine we want + if (d->engineData->engines[script]) + return; + + QFontEngine *fe = QFontCache::instance()->findEngine(key); + + // set it to the actual pointsize, so QFontInfo will do the right thing + if (req.pointSize < 0) + req.pointSize = req.pixelSize*72./d->dpi; + + if (!fe) { + if (qt_enable_test_font && req.family == QLatin1String("__Qt__Box__Engine__")) { + fe = new QTestFontEngine(req.pixelSize); + fe->fontDef = req; + } else { + QMutexLocker locker(fontDatabaseMutex()); + if (!privateDb()->count) + initializeDb(); + fe = loadWin(d, script, req); + } + if (!fe) { + fe = new QFontEngineBox(req.pixelSize); + fe->fontDef = QFontDef(); + } + } + d->engineData->engines[script] = fe; + fe->ref.ref(); + QFontCache::instance()->insertEngine(key, fe); +} + +#if !defined(FR_PRIVATE) +#define FR_PRIVATE 0x10 +#endif + +typedef int (WINAPI *PtrAddFontResourceExW)(LPCWSTR, DWORD, PVOID); +typedef HANDLE (WINAPI *PtrAddFontMemResourceEx)(PVOID, DWORD, PVOID, DWORD *); +typedef BOOL (WINAPI *PtrRemoveFontResourceExW)(LPCWSTR, DWORD, PVOID); +typedef BOOL (WINAPI *PtrRemoveFontMemResourceEx)(HANDLE); + +static QList getTrueTypeFontOffsets(const uchar *fontData) +{ + QList offsets; + const quint32 headerTag = *reinterpret_cast(fontData); + if (headerTag != MAKE_TAG('t', 't', 'c', 'f')) { + if (headerTag != MAKE_TAG(0, 1, 0, 0) + && headerTag != MAKE_TAG('O', 'T', 'T', 'O') + && headerTag != MAKE_TAG('t', 'r', 'u', 'e') + && headerTag != MAKE_TAG('t', 'y', 'p', '1')) + return offsets; + offsets << 0; + return offsets; + } + const quint32 numFonts = qFromBigEndian(fontData + 8); + for (uint i = 0; i < numFonts; ++i) { + offsets << qFromBigEndian(fontData + 12 + i * 4); + } + return offsets; +} + +static void getFontTable(const uchar *fileBegin, const uchar *data, quint32 tag, const uchar **table, quint32 *length) +{ + const quint16 numTables = qFromBigEndian(data + 4); + for (uint i = 0; i < numTables; ++i) { + const quint32 offset = 12 + 16 * i; + if (*reinterpret_cast(data + offset) == tag) { + *table = fileBegin + qFromBigEndian(data + offset + 8); + *length = qFromBigEndian(data + offset + 12); + return; + } + } + *table = 0; + *length = 0; + return; +} + +static void getFamiliesAndSignatures(const QByteArray &fontData, QFontDatabasePrivate::ApplicationFont *appFont) +{ + const uchar *data = reinterpret_cast(fontData.constData()); + + QList offsets = getTrueTypeFontOffsets(data); + if (offsets.isEmpty()) + return; + + for (int i = 0; i < offsets.count(); ++i) { + const uchar *font = data + offsets.at(i); + const uchar *table; + quint32 length; + getFontTable(data, font, MAKE_TAG('n', 'a', 'm', 'e'), &table, &length); + if (!table) + continue; + QString name = getEnglishName(table, length); + if (name.isEmpty()) + continue; + + appFont->families << name; + FONTSIGNATURE signature; + getFontTable(data, font, MAKE_TAG('O', 'S', '/', '2'), &table, &length); + if (table && length >= 86) { + // See also qfontdatabase_mac.cpp, offsets taken from OS/2 table in the TrueType spec + signature.fsUsb[0] = qFromBigEndian(table + 42); + signature.fsUsb[1] = qFromBigEndian(table + 46); + signature.fsUsb[2] = qFromBigEndian(table + 50); + signature.fsUsb[3] = qFromBigEndian(table + 54); + + signature.fsCsb[0] = qFromBigEndian(table + 78); + signature.fsCsb[1] = qFromBigEndian(table + 82); + } else { + memset(&signature, 0, sizeof(signature)); + } + appFont->signatures << signature; + } +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt) +{ + if(!fnt->data.isEmpty()) { +#ifndef Q_OS_WINCE + PtrAddFontMemResourceEx ptrAddFontMemResourceEx = (PtrAddFontMemResourceEx)QSystemLibrary::resolve(QLatin1String("gdi32"), + "AddFontMemResourceEx"); + if (!ptrAddFontMemResourceEx) + return; +#endif + getFamiliesAndSignatures(fnt->data, fnt); + if (fnt->families.isEmpty()) + return; + +#ifdef Q_OS_WINCE + HANDLE handle = 0; + + { +#ifdef QT_NO_TEMPORARYFILE + wchar_t lpBuffer[MAX_PATH]; + GetTempPath(MAX_PATH, lpBuffer); + QString s = QString::fromWCharArray(lpBuffer); + QFile tempfile(s + QLatin1String("/font") + QString::number(GetTickCount()) + QLatin1String(".ttf")); + if (!tempfile.open(QIODevice::ReadWrite)) +#else + QTemporaryFile tempfile(QLatin1String("XXXXXXXX.ttf")); + if (!tempfile.open()) +#endif // QT_NO_TEMPORARYFILE + return; + if (tempfile.write(fnt->data) == -1) + return; + +#ifndef QT_NO_TEMPORARYFILE + tempfile.setAutoRemove(false); +#endif + fnt->fileName = QFileInfo(tempfile.fileName()).absoluteFilePath(); + } + + if (AddFontResource((LPCWSTR)fnt->fileName.utf16()) == 0) { + QFile(fnt->fileName).remove(); + return; + } +#else + DWORD dummy = 0; + HANDLE handle = ptrAddFontMemResourceEx((void *)fnt->data.constData(), fnt->data.size(), 0, + &dummy); + if (handle == 0) + return; +#endif // Q_OS_WINCE + + fnt->handle = handle; + fnt->data = QByteArray(); + fnt->memoryFont = true; + } else { + QFile f(fnt->fileName); + if (!f.open(QIODevice::ReadOnly)) + return; + QByteArray data = f.readAll(); + f.close(); + getFamiliesAndSignatures(data, fnt); + +#ifdef Q_OS_WINCE + QFileInfo fileinfo(fnt->fileName); + fnt->fileName = fileinfo.absoluteFilePath(); + if (AddFontResource((LPCWSTR)fnt->fileName.utf16()) == 0) + return; +#else + PtrAddFontResourceExW ptrAddFontResourceExW = (PtrAddFontResourceExW)QSystemLibrary::resolve(QLatin1String("gdi32"), + "AddFontResourceExW"); + if (!ptrAddFontResourceExW + || ptrAddFontResourceExW((wchar_t*)fnt->fileName.utf16(), FR_PRIVATE, 0) == 0) + return; +#endif // Q_OS_WINCE + + fnt->memoryFont = false; + } +} + +bool QFontDatabase::removeApplicationFont(int handle) +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (handle < 0 || handle >= db->applicationFonts.count()) + return false; + + const QFontDatabasePrivate::ApplicationFont font = db->applicationFonts.at(handle); + db->applicationFonts[handle] = QFontDatabasePrivate::ApplicationFont(); + if (font.memoryFont) { +#ifdef Q_OS_WINCE + bool removeSucceeded = RemoveFontResource((LPCWSTR)font.fileName.utf16()); + QFile tempfile(font.fileName); + tempfile.remove(); + if (!removeSucceeded) + return false; +#else + PtrRemoveFontMemResourceEx ptrRemoveFontMemResourceEx = (PtrRemoveFontMemResourceEx)QSystemLibrary::resolve(QLatin1String("gdi32"), + "RemoveFontMemResourceEx"); + if (!ptrRemoveFontMemResourceEx + || !ptrRemoveFontMemResourceEx(font.handle)) + return false; +#endif // Q_OS_WINCE + } else { +#ifdef Q_OS_WINCE + if (!RemoveFontResource((LPCWSTR)font.fileName.utf16())) + return false; +#else + PtrRemoveFontResourceExW ptrRemoveFontResourceExW = (PtrRemoveFontResourceExW)QSystemLibrary::resolve(QLatin1String("gdi32"), + "RemoveFontResourceExW"); + if (!ptrRemoveFontResourceExW + || !ptrRemoveFontResourceExW((LPCWSTR)font.fileName.utf16(), FR_PRIVATE, 0)) + return false; +#endif // Q_OS_WINCE + } + + db->invalidate(); + return true; +} + +bool QFontDatabase::removeAllApplicationFonts() +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + for (int i = 0; i < db->applicationFonts.count(); ++i) + if (!removeApplicationFont(i)) + return false; + return true; +} + +bool QFontDatabase::supportsThreadedFontRendering() +{ + return true; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontdatabase_x11.cpp b/src/gui/text/qfontdatabase_x11.cpp new file mode 100644 index 0000000000..0c0c4c8343 --- /dev/null +++ b/src/gui/text/qfontdatabase_x11.cpp @@ -0,0 +1,2146 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include "qx11info_x11.h" +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#ifndef QT_NO_FONTCONFIG +#include +#include FT_FREETYPE_H + +#if FC_VERSION >= 20402 +#include +#endif +#endif + +QT_BEGIN_NAMESPACE + +// from qfont_x11.cpp +extern double qt_pointSize(double pixelSize, int dpi); +extern double qt_pixelSize(double pointSize, int dpi); + +// from qapplication.cpp +extern bool qt_is_gui_used; + +static inline void capitalize (char *s) +{ + bool space = true; + while(*s) { + if (space) + *s = toupper(*s); + space = (*s == ' '); + ++s; + } +} + + +/* + To regenerate the writingSystems_for_xlfd_encoding table, run + 'util/unicode/x11/makeencodings' and paste the generated + 'encodings.c' here. +*/ +// ----- begin of generated code ----- + +#define make_tag( c1, c2, c3, c4 ) \ + ((((unsigned int)c1)<<24) | (((unsigned int)c2)<<16) | \ + (((unsigned int)c3)<<8) | ((unsigned int)c4)) + +struct XlfdEncoding { + const char *name; + int id; + int mib; + unsigned int hash1; + unsigned int hash2; +}; + +static const XlfdEncoding xlfd_encoding[] = { + { "iso8859-1", 0, 4, make_tag('i','s','o','8'), make_tag('5','9','-','1') }, + { "iso8859-2", 1, 5, make_tag('i','s','o','8'), make_tag('5','9','-','2') }, + { "iso8859-3", 2, 6, make_tag('i','s','o','8'), make_tag('5','9','-','3') }, + { "iso8859-4", 3, 7, make_tag('i','s','o','8'), make_tag('5','9','-','4') }, + { "iso8859-9", 4, 12, make_tag('i','s','o','8'), make_tag('5','9','-','9') }, + { "iso8859-10", 5, 13, make_tag('i','s','o','8'), make_tag('9','-','1','0') }, + { "iso8859-13", 6, 109, make_tag('i','s','o','8'), make_tag('9','-','1','3') }, + { "iso8859-14", 7, 110, make_tag('i','s','o','8'), make_tag('9','-','1','4') }, + { "iso8859-15", 8, 111, make_tag('i','s','o','8'), make_tag('9','-','1','5') }, + { "hp-roman8", 9, 2004, make_tag('h','p','-','r'), make_tag('m','a','n','8') }, + { "iso8859-5", 10, 8, make_tag('i','s','o','8'), make_tag('5','9','-','5') }, + { "*-cp1251", 11, 2251, 0, make_tag('1','2','5','1') }, + { "koi8-ru", 12, 2084, make_tag('k','o','i','8'), make_tag('8','-','r','u') }, + { "koi8-u", 13, 2088, make_tag('k','o','i','8'), make_tag('i','8','-','u') }, + { "koi8-r", 14, 2084, make_tag('k','o','i','8'), make_tag('i','8','-','r') }, + { "iso8859-7", 15, 10, make_tag('i','s','o','8'), make_tag('5','9','-','7') }, + { "iso8859-8", 16, 85, make_tag('i','s','o','8'), make_tag('5','9','-','8') }, + { "gb18030-0", 17, -114, make_tag('g','b','1','8'), make_tag('3','0','-','0') }, + { "gb18030.2000-0", 18, -113, make_tag('g','b','1','8'), make_tag('0','0','-','0') }, + { "gbk-0", 19, -113, make_tag('g','b','k','-'), make_tag('b','k','-','0') }, + { "gb2312.*-0", 20, 57, make_tag('g','b','2','3'), 0 }, + { "jisx0201*-0", 21, 15, make_tag('j','i','s','x'), 0 }, + { "jisx0208*-0", 22, 63, make_tag('j','i','s','x'), 0 }, + { "ksc5601*-*", 23, 36, make_tag('k','s','c','5'), 0 }, + { "big5hkscs-0", 24, -2101, make_tag('b','i','g','5'), make_tag('c','s','-','0') }, + { "hkscs-1", 25, -2101, make_tag('h','k','s','c'), make_tag('c','s','-','1') }, + { "big5*-*", 26, -2026, make_tag('b','i','g','5'), 0 }, + { "tscii-*", 27, 2028, make_tag('t','s','c','i'), 0 }, + { "tis620*-*", 28, 2259, make_tag('t','i','s','6'), 0 }, + { "iso8859-11", 29, 2259, make_tag('i','s','o','8'), make_tag('9','-','1','1') }, + { "mulelao-1", 30, -4242, make_tag('m','u','l','e'), make_tag('a','o','-','1') }, + { "ethiopic-unicode", 31, 0, make_tag('e','t','h','i'), make_tag('c','o','d','e') }, + { "iso10646-1", 32, 0, make_tag('i','s','o','1'), make_tag('4','6','-','1') }, + { "unicode-*", 33, 0, make_tag('u','n','i','c'), 0 }, + { "*-symbol", 34, 0, 0, make_tag('m','b','o','l') }, + { "*-fontspecific", 35, 0, 0, make_tag('i','f','i','c') }, + { "fontspecific-*", 36, 0, make_tag('f','o','n','t'), 0 }, + { 0, 0, 0, 0, 0 } +}; + +static const char writingSystems_for_xlfd_encoding[sizeof(xlfd_encoding)][QFontDatabase::WritingSystemsCount] = { + // iso8859-1 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-2 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-3 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-4 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-9 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-10 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-13 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-14 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-15 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // hp-roman8 + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-5 + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // *-cp1251 + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // koi8-ru + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // koi8-u + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // koi8-r + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-7 + { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-8 + { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // gb18030-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0 }, + // gb18030.2000-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0 }, + // gbk-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0 }, + // gb2312.*-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0 }, + // jisx0201*-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0 }, + // jisx0208*-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0 }, + // ksc5601*-* + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0 }, + // big5hkscs-0 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0 }, + // hkscs-1 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0 }, + // big5*-* + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0 }, + // tscii-* + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // tis620*-* + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso8859-11 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // mulelao-1 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // ethiopic-unicode + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0 }, + // iso10646-1 + { 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, + 0, 0 }, + // unicode-* + { 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, + 0, 0 }, + // *-symbol + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0 }, + // *-fontspecific + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0 }, + // fontspecific-* + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0 } + +}; + +// ----- end of generated code ----- + + +const int numEncodings = sizeof(xlfd_encoding) / sizeof(XlfdEncoding) - 1; + +int qt_xlfd_encoding_id(const char *encoding) +{ + // qDebug("looking for encoding id for '%s'", encoding); + int len = strlen(encoding); + if (len < 4) + return -1; + unsigned int hash1 = make_tag(encoding[0], encoding[1], encoding[2], encoding[3]); + const char *ch = encoding + len - 4; + unsigned int hash2 = make_tag(ch[0], ch[1], ch[2], ch[3]); + + const XlfdEncoding *enc = xlfd_encoding; + for (; enc->name; ++enc) { + if ((enc->hash1 && enc->hash1 != hash1) || + (enc->hash2 && enc->hash2 != hash2)) + continue; + // hashes match, do a compare if strings match + // the enc->name can contain '*'s we have to interpret correctly + const char *n = enc->name; + const char *e = encoding; + while (1) { + // qDebug("bol: *e='%c', *n='%c'", *e, *n); + if (*e == '\0') { + if (*n) + break; + // qDebug("found encoding id %d", enc->id); + return enc->id; + } + if (*e == *n) { + ++e; + ++n; + continue; + } + if (*n != '*') + break; + ++n; + // qDebug("skip: *e='%c', *n='%c'", *e, *n); + while (*e && *e != *n) + ++e; + } + } + // qDebug("couldn't find encoding %s", encoding); + return -1; +} + +int qt_mib_for_xlfd_encoding(const char *encoding) +{ + int id = qt_xlfd_encoding_id(encoding); + if (id != -1) return xlfd_encoding[id].mib; + return 0; +} + +int qt_encoding_id_for_mib(int mib) +{ + const XlfdEncoding *enc = xlfd_encoding; + for (; enc->name; ++enc) { + if (enc->mib == mib) + return enc->id; + } + return -1; +} + +static const char * xlfd_for_id(int id) +{ + // special case: -1 returns the "*-*" encoding, allowing us to do full + // database population in a single X server round trip. + if (id < 0 || id > numEncodings) + return "*-*"; + return xlfd_encoding[id].name; +} + +enum XLFDFieldNames { + Foundry, + Family, + Weight, + Slant, + Width, + AddStyle, + PixelSize, + PointSize, + ResolutionX, + ResolutionY, + Spacing, + AverageWidth, + CharsetRegistry, + CharsetEncoding, + NFontFields +}; + +// Splits an X font name into fields separated by '-' +static bool parseXFontName(char *fontName, char **tokens) +{ + if (! fontName || fontName[0] == '0' || fontName[0] != '-') { + tokens[0] = 0; + return false; + } + + int i; + ++fontName; + for (i = 0; i < NFontFields && fontName && fontName[0]; ++i) { + tokens[i] = fontName; + for (;; ++fontName) { + if (*fontName == '-') + break; + if (! *fontName) { + fontName = 0; + break; + } + } + + if (fontName) *fontName++ = '\0'; + } + + if (i < NFontFields) { + for (int j = i ; j < NFontFields; ++j) + tokens[j] = 0; + return false; + } + + return true; +} + +static inline bool isZero(char *x) +{ + return (x[0] == '0' && x[1] == 0); +} + +static inline bool isScalable(char **tokens) +{ + return (isZero(tokens[PixelSize]) && + isZero(tokens[PointSize]) && + isZero(tokens[AverageWidth])); +} + +static inline bool isSmoothlyScalable(char **tokens) +{ + return (isZero(tokens[ResolutionX]) && + isZero(tokens[ResolutionY])); +} + +static inline bool isFixedPitch(char **tokens) +{ + return (tokens[Spacing][0] == 'm' || + tokens[Spacing][0] == 'c' || + tokens[Spacing][0] == 'M' || + tokens[Spacing][0] == 'C'); +} + +/* + Fills in a font definition (QFontDef) from an XLFD (X Logical Font + Description). + + Returns true if the given xlfd is valid. +*/ +bool qt_fillFontDef(const QByteArray &xlfd, QFontDef *fd, int dpi, QtFontDesc *desc) +{ + char *tokens[NFontFields]; + QByteArray buffer = xlfd; + if (! parseXFontName(buffer.data(), tokens)) + return false; + + capitalize(tokens[Family]); + capitalize(tokens[Foundry]); + + fd->styleStrategy |= QFont::NoAntialias; + fd->family = QString::fromLatin1(tokens[Family]); + QString foundry = QString::fromLatin1(tokens[Foundry]); + if (! foundry.isEmpty() && foundry != QLatin1String("*") && (!desc || desc->family->count > 1)) + fd->family += + QLatin1String(" [") + foundry + QLatin1Char(']'); + + if (qstrlen(tokens[AddStyle]) > 0) + fd->addStyle = QString::fromLatin1(tokens[AddStyle]); + else + fd->addStyle.clear(); + + fd->pointSize = atoi(tokens[PointSize])/10.; + fd->styleHint = QFont::AnyStyle; // ### any until we match families + + char slant = tolower((uchar) tokens[Slant][0]); + fd->style = (slant == 'o' ? QFont::StyleOblique : (slant == 'i' ? QFont::StyleItalic : QFont::StyleNormal)); + char fixed = tolower((uchar) tokens[Spacing][0]); + fd->fixedPitch = (fixed == 'm' || fixed == 'c'); + fd->weight = getFontWeight(QLatin1String(tokens[Weight])); + + int r = atoi(tokens[ResolutionY]); + fd->pixelSize = atoi(tokens[PixelSize]); + // not "0" or "*", or required DPI + if (r && fd->pixelSize && r != dpi) { + // calculate actual pointsize for display DPI + fd->pointSize = qt_pointSize(fd->pixelSize, dpi); + } else if (fd->pixelSize == 0 && fd->pointSize) { + // calculate pixel size from pointsize/dpi + fd->pixelSize = qRound(qt_pixelSize(fd->pointSize, dpi)); + } + + return true; +} + +/* + Fills in a font definition (QFontDef) from the font properties in an + XFontStruct. + + Returns true if the QFontDef could be filled with properties from + the XFontStruct. +*/ +static bool qt_fillFontDef(XFontStruct *fs, QFontDef *fd, int dpi, QtFontDesc *desc) +{ + unsigned long value; + if (!fs || !XGetFontProperty(fs, XA_FONT, &value)) + return false; + + char *n = XGetAtomName(QX11Info::display(), value); + QByteArray xlfd(n); + if (n) + XFree(n); + return qt_fillFontDef(xlfd.toLower(), fd, dpi, desc); +} + + +static QtFontStyle::Key getStyle(char ** tokens) +{ + QtFontStyle::Key key; + + char slant0 = tolower((uchar) tokens[Slant][0]); + + if (slant0 == 'r') { + if (tokens[Slant][1]) { + char slant1 = tolower((uchar) tokens[Slant][1]); + + if (slant1 == 'o') + key.style = QFont::StyleOblique; + else if (slant1 == 'i') + key.style = QFont::StyleItalic; + } + } else if (slant0 == 'o') + key.style = QFont::StyleOblique; + else if (slant0 == 'i') + key.style = QFont::StyleItalic; + + key.weight = getFontWeight(QLatin1String(tokens[Weight])); + + if (qstrcmp(tokens[Width], "normal") == 0) { + key.stretch = 100; + } else if (qstrcmp(tokens[Width], "semi condensed") == 0 || + qstrcmp(tokens[Width], "semicondensed") == 0) { + key.stretch = 90; + } else if (qstrcmp(tokens[Width], "condensed") == 0) { + key.stretch = 80; + } else if (qstrcmp(tokens[Width], "narrow") == 0) { + key.stretch = 60; + } + + return key; +} + + +static bool xlfdsFullyLoaded = false; +static unsigned char encodingLoaded[numEncodings]; + +static void loadXlfds(const char *reqFamily, int encoding_id) +{ + QFontDatabasePrivate *db = privateDb(); + QtFontFamily *fontFamily = reqFamily ? db->family(QLatin1String(reqFamily)) : 0; + + // make sure we don't load twice + if ((encoding_id == -1 && xlfdsFullyLoaded) + || (encoding_id != -1 && encodingLoaded[encoding_id])) + return; + if (fontFamily && fontFamily->xlfdLoaded) + return; + + int fontCount; + // force the X server to give us XLFDs + QByteArray xlfd_pattern("-*-"); + xlfd_pattern += (reqFamily && reqFamily[0] != '\0') ? reqFamily : "*"; + xlfd_pattern += "-*-*-*-*-*-*-*-*-*-*-"; + xlfd_pattern += xlfd_for_id(encoding_id); + + char **fontList = XListFonts(QX11Info::display(), + xlfd_pattern, + 0xffff, &fontCount); + // qDebug("requesting xlfd='%s', got %d fonts", xlfd_pattern.data(), fontCount); + + + char *tokens[NFontFields]; + + for(int i = 0 ; i < fontCount ; i++) { + if (! parseXFontName(fontList[i], tokens)) + continue; + + // get the encoding_id for this xlfd. we need to do this + // here, since we can pass -1 to this function to do full + // database population + *(tokens[CharsetEncoding] - 1) = '-'; + int encoding_id = qt_xlfd_encoding_id(tokens[CharsetRegistry]); + if (encoding_id == -1) + continue; + + char *familyName = tokens[Family]; + capitalize(familyName); + char *foundryName = tokens[Foundry]; + capitalize(foundryName); + QtFontStyle::Key styleKey = getStyle(tokens); + + bool smooth_scalable = false; + bool bitmap_scalable = false; + if (isScalable(tokens)) { + if (isSmoothlyScalable(tokens)) + smooth_scalable = true; + else + bitmap_scalable = true; + } + uint pixelSize = atoi(tokens[PixelSize]); + uint xpointSize = atoi(tokens[PointSize]); + uint xres = atoi(tokens[ResolutionX]); + uint yres = atoi(tokens[ResolutionY]); + uint avgwidth = atoi(tokens[AverageWidth]); + bool fixedPitch = isFixedPitch(tokens); + + if (avgwidth == 0 && pixelSize != 0) { + /* + Ignore bitmap scalable fonts that are automatically + generated by some X servers. We know they are bitmap + scalable because even though they have a specified pixel + size, the average width is zero. + */ + continue; + } + + QtFontFamily *family = fontFamily ? fontFamily : db->family(QLatin1String(familyName), true); + family->fontFileIndex = -1; + family->symbol_checked = true; + QtFontFoundry *foundry = family->foundry(QLatin1String(foundryName), true); + QtFontStyle *style = foundry->style(styleKey, true); + + delete [] style->weightName; + style->weightName = qstrdup(tokens[Weight]); + delete [] style->setwidthName; + style->setwidthName = qstrdup(tokens[Width]); + + if (smooth_scalable) { + style->smoothScalable = true; + style->bitmapScalable = false; + pixelSize = SMOOTH_SCALABLE; + } + if (!style->smoothScalable && bitmap_scalable) + style->bitmapScalable = true; + if (!fixedPitch) + family->fixedPitch = false; + + QtFontSize *size = style->pixelSize(pixelSize, true); + QtFontEncoding *enc = + size->encodingID(encoding_id, xpointSize, xres, yres, avgwidth, true); + enc->pitch = *tokens[Spacing]; + if (!enc->pitch) enc->pitch = '*'; + + for (int i = 0; i < QFontDatabase::WritingSystemsCount; ++i) { + if (writingSystems_for_xlfd_encoding[encoding_id][i]) + family->writingSystems[i] = QtFontFamily::Supported; + } + } + if (!reqFamily) { + // mark encoding as loaded + if (encoding_id == -1) + xlfdsFullyLoaded = true; + else + encodingLoaded[encoding_id] = true; + } + + XFreeFontNames(fontList); +} + + +#ifndef QT_NO_FONTCONFIG + +#ifndef FC_WIDTH +#define FC_WIDTH "width" +#endif + +static int getFCWeight(int fc_weight) +{ + int qtweight = QFont::Black; + if (fc_weight <= (FC_WEIGHT_LIGHT + FC_WEIGHT_MEDIUM) / 2) + qtweight = QFont::Light; + else if (fc_weight <= (FC_WEIGHT_MEDIUM + FC_WEIGHT_DEMIBOLD) / 2) + qtweight = QFont::Normal; + else if (fc_weight <= (FC_WEIGHT_DEMIBOLD + FC_WEIGHT_BOLD) / 2) + qtweight = QFont::DemiBold; + else if (fc_weight <= (FC_WEIGHT_BOLD + FC_WEIGHT_BLACK) / 2) + qtweight = QFont::Bold; + + return qtweight; +} + +QFontDef qt_FcPatternToQFontDef(FcPattern *pattern, const QFontDef &request) +{ + QFontDef fontDef; + fontDef.styleStrategy = request.styleStrategy; + + fontDef.hintingPreference = request.hintingPreference; + FcChar8 *value = 0; + if (FcPatternGetString(pattern, FC_FAMILY, 0, &value) == FcResultMatch) { + fontDef.family = QString::fromUtf8(reinterpret_cast(value)); + } + + double dpi; + if (FcPatternGetDouble(pattern, FC_DPI, 0, &dpi) != FcResultMatch) { + if (X11->display) + dpi = QX11Info::appDpiY(); + else + dpi = qt_defaultDpiY(); + } + + double size; + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &size) == FcResultMatch) + fontDef.pixelSize = size; + else + fontDef.pixelSize = 12; + + fontDef.pointSize = qt_pointSize(fontDef.pixelSize, qRound(dpi)); + + /* ### + fontDef.styleHint + */ + + int weight; + if (FcPatternGetInteger(pattern, FC_WEIGHT, 0, &weight) != FcResultMatch) + weight = FC_WEIGHT_MEDIUM; + fontDef.weight = getFCWeight(weight); + + int slant; + if (FcPatternGetInteger(pattern, FC_SLANT, 0, &slant) != FcResultMatch) + slant = FC_SLANT_ROMAN; + fontDef.style = (slant == FC_SLANT_ITALIC) + ? QFont::StyleItalic + : ((slant == FC_SLANT_OBLIQUE) + ? QFont::StyleOblique + : QFont::StyleNormal); + + + FcBool scalable; + if (FcPatternGetBool(pattern, FC_SCALABLE, 0, &scalable) != FcResultMatch) + scalable = false; + if (scalable) { + fontDef.stretch = request.stretch; + fontDef.style = request.style; + } else { + int width; + if (FcPatternGetInteger(pattern, FC_WIDTH, 0, &width) == FcResultMatch) + fontDef.stretch = width; + else + fontDef.stretch = 100; + } + + int spacing; + if (FcPatternGetInteger(pattern, FC_SPACING, 0, &spacing) == FcResultMatch) { + fontDef.fixedPitch = (spacing >= FC_MONO); + fontDef.ignorePitch = false; + } else { + fontDef.ignorePitch = true; + } + + return fontDef; +} + +static const char *specialLanguages[] = { + "en", // Common + "el", // Greek + "ru", // Cyrillic + "hy", // Armenian + "he", // Hebrew + "ar", // Arabic + "syr", // Syriac + "div", // Thaana + "hi", // Devanagari + "bn", // Bengali + "pa", // Gurmukhi + "gu", // Gujarati + "or", // Oriya + "ta", // Tamil + "te", // Telugu + "kn", // Kannada + "ml", // Malayalam + "si", // Sinhala + "th", // Thai + "lo", // Lao + "bo", // Tibetan + "my", // Myanmar + "ka", // Georgian + "ko", // Hangul + "", // Ogham + "", // Runic + "km", // Khmer + "" // N'Ko +}; +enum { SpecialLanguageCount = sizeof(specialLanguages) / sizeof(const char *) }; + +static const ushort specialChars[] = { + 0, // English + 0, // Greek + 0, // Cyrillic + 0, // Armenian + 0, // Hebrew + 0, // Arabic + 0, // Syriac + 0, // Thaana + 0, // Devanagari + 0, // Bengali + 0, // Gurmukhi + 0, // Gujarati + 0, // Oriya + 0, // Tamil + 0xc15, // Telugu + 0xc95, // Kannada + 0xd15, // Malayalam + 0xd9a, // Sinhala + 0, // Thai + 0, // Lao + 0, // Tibetan + 0x1000, // Myanmar + 0, // Georgian + 0, // Hangul + 0x1681, // Ogham + 0x16a0, // Runic + 0, // Khmer + 0x7ca // N'Ko +}; +enum { SpecialCharCount = sizeof(specialChars) / sizeof(ushort) }; + +// this could become a list of all languages used for each writing +// system, instead of using the single most common language. +static const char *languageForWritingSystem[] = { + 0, // Any + "en", // Latin + "el", // Greek + "ru", // Cyrillic + "hy", // Armenian + "he", // Hebrew + "ar", // Arabic + "syr", // Syriac + "div", // Thaana + "hi", // Devanagari + "bn", // Bengali + "pa", // Gurmukhi + "gu", // Gujarati + "or", // Oriya + "ta", // Tamil + "te", // Telugu + "kn", // Kannada + "ml", // Malayalam + "si", // Sinhala + "th", // Thai + "lo", // Lao + "bo", // Tibetan + "my", // Myanmar + "ka", // Georgian + "km", // Khmer + "zh-cn", // SimplifiedChinese + "zh-tw", // TraditionalChinese + "ja", // Japanese + "ko", // Korean + "vi", // Vietnamese + 0, // Symbol + 0, // Ogham + 0, // Runic + 0 // N'Ko +}; +enum { LanguageCount = sizeof(languageForWritingSystem) / sizeof(const char *) }; + +// Unfortunately FontConfig doesn't know about some languages. We have to test these through the +// charset. The lists below contain the systems where we need to do this. +static const ushort sampleCharForWritingSystem[] = { + 0, // Any + 0, // Latin + 0, // Greek + 0, // Cyrillic + 0, // Armenian + 0, // Hebrew + 0, // Arabic + 0, // Syriac + 0, // Thaana + 0, // Devanagari + 0, // Bengali + 0, // Gurmukhi + 0, // Gujarati + 0, // Oriya + 0, // Tamil + 0xc15, // Telugu + 0xc95, // Kannada + 0xd15, // Malayalam + 0xd9a, // Sinhala + 0, // Thai + 0, // Lao + 0, // Tibetan + 0x1000, // Myanmar + 0, // Georgian + 0, // Khmer + 0, // SimplifiedChinese + 0, // TraditionalChinese + 0, // Japanese + 0, // Korean + 0, // Vietnamese + 0, // Symbol + 0x1681, // Ogham + 0x16a0, // Runic + 0x7ca // N'Ko +}; +enum { SampleCharCount = sizeof(sampleCharForWritingSystem) / sizeof(ushort) }; + +// Newer FontConfig let's us sort out fonts that contain certain glyphs, but no +// open type tables for is directly. Do this so we don't pick some strange +// pseudo unicode font +static const char *openType[] = { + 0, // Any + 0, // Latin + 0, // Greek + 0, // Cyrillic + 0, // Armenian + 0, // Hebrew + 0, // Arabic + "syrc", // Syriac + "thaa", // Thaana + "deva", // Devanagari + "beng", // Bengali + "guru", // Gurmukhi + "gurj", // Gujarati + "orya", // Oriya + "taml", // Tamil + "telu", // Telugu + "knda", // Kannada + "mlym", // Malayalam + "sinh", // Sinhala + 0, // Thai + 0, // Lao + "tibt", // Tibetan + "mymr", // Myanmar + 0, // Georgian + "khmr", // Khmer + 0, // SimplifiedChinese + 0, // TraditionalChinese + 0, // Japanese + 0, // Korean + 0, // Vietnamese + 0, // Symbol + 0, // Ogham + 0, // Runic + "nko " // N'Ko +}; +enum { OpenTypeCount = sizeof(openType) / sizeof(const char *) }; + + +static void loadFontConfig() +{ + Q_ASSERT_X(X11, "QFontDatabase", + "A QApplication object needs to be constructed before FontConfig is used."); + if (!X11->has_fontconfig) + return; + + Q_ASSERT_X(int(QUnicodeTables::ScriptCount) == SpecialLanguageCount, + "QFontDatabase", "New scripts have been added."); + Q_ASSERT_X(int(QUnicodeTables::ScriptCount) == SpecialCharCount, + "QFontDatabase", "New scripts have been added."); + Q_ASSERT_X(int(QFontDatabase::WritingSystemsCount) == LanguageCount, + "QFontDatabase", "New writing systems have been added."); + Q_ASSERT_X(int(QFontDatabase::WritingSystemsCount) == SampleCharCount, + "QFontDatabase", "New writing systems have been added."); + Q_ASSERT_X(int(QFontDatabase::WritingSystemsCount) == OpenTypeCount, + "QFontDatabase", "New writing systems have been added."); + + QFontDatabasePrivate *db = privateDb(); + FcFontSet *fonts; + + FcPattern *pattern = FcPatternCreate(); + FcDefaultSubstitute(pattern); + FcChar8 *lang = 0; + if (FcPatternGetString(pattern, FC_LANG, 0, &lang) == FcResultMatch) + db->systemLang = QString::fromUtf8((const char *) lang); + FcPatternDestroy(pattern); + + QString familyName; + FcChar8 *value = 0; + int weight_value; + int slant_value; + int spacing_value; + FcChar8 *file_value; + int index_value; + FcChar8 *foundry_value; + FcBool scalable; + + { + FcObjectSet *os = FcObjectSetCreate(); + FcPattern *pattern = FcPatternCreate(); + const char *properties [] = { + FC_FAMILY, FC_WEIGHT, FC_SLANT, + FC_SPACING, FC_FILE, FC_INDEX, + FC_LANG, FC_CHARSET, FC_FOUNDRY, FC_SCALABLE, FC_PIXEL_SIZE, FC_WEIGHT, + FC_WIDTH, +#if FC_VERSION >= 20297 + FC_CAPABILITY, +#endif + (const char *)0 + }; + const char **p = properties; + while (*p) { + FcObjectSetAdd(os, *p); + ++p; + } + fonts = FcFontList(0, pattern, os); + FcObjectSetDestroy(os); + FcPatternDestroy(pattern); + } + + for (int i = 0; i < fonts->nfont; i++) { + if (FcPatternGetString(fonts->fonts[i], FC_FAMILY, 0, &value) != FcResultMatch) + continue; + // capitalize(value); + familyName = QString::fromUtf8((const char *)value); + slant_value = FC_SLANT_ROMAN; + weight_value = FC_WEIGHT_MEDIUM; + spacing_value = FC_PROPORTIONAL; + file_value = 0; + index_value = 0; + scalable = FcTrue; + + if (FcPatternGetInteger (fonts->fonts[i], FC_SLANT, 0, &slant_value) != FcResultMatch) + slant_value = FC_SLANT_ROMAN; + if (FcPatternGetInteger (fonts->fonts[i], FC_WEIGHT, 0, &weight_value) != FcResultMatch) + weight_value = FC_WEIGHT_MEDIUM; + if (FcPatternGetInteger (fonts->fonts[i], FC_SPACING, 0, &spacing_value) != FcResultMatch) + spacing_value = FC_PROPORTIONAL; + if (FcPatternGetString (fonts->fonts[i], FC_FILE, 0, &file_value) != FcResultMatch) + file_value = 0; + if (FcPatternGetInteger (fonts->fonts[i], FC_INDEX, 0, &index_value) != FcResultMatch) + index_value = 0; + if (FcPatternGetBool(fonts->fonts[i], FC_SCALABLE, 0, &scalable) != FcResultMatch) + scalable = FcTrue; + if (FcPatternGetString(fonts->fonts[i], FC_FOUNDRY, 0, &foundry_value) != FcResultMatch) + foundry_value = 0; + QtFontFamily *family = db->family(familyName, true); + + FcLangSet *langset = 0; + FcResult res = FcPatternGetLangSet(fonts->fonts[i], FC_LANG, 0, &langset); + if (res == FcResultMatch) { + for (int i = 1; i < LanguageCount; ++i) { + const FcChar8 *lang = (const FcChar8*) languageForWritingSystem[i]; + if (!lang) { + family->writingSystems[i] |= QtFontFamily::UnsupportedFT; + } else { + FcLangResult langRes = FcLangSetHasLang(langset, lang); + if (langRes != FcLangDifferentLang) + family->writingSystems[i] = QtFontFamily::Supported; + else + family->writingSystems[i] |= QtFontFamily::UnsupportedFT; + } + } + family->writingSystems[QFontDatabase::Other] = QtFontFamily::UnsupportedFT; + family->ftWritingSystemCheck = true; + } else { + // we set Other to supported for symbol fonts. It makes no + // sense to merge these with other ones, as they are + // special in a way. + for (int i = 1; i < LanguageCount; ++i) + family->writingSystems[i] |= QtFontFamily::UnsupportedFT; + family->writingSystems[QFontDatabase::Other] = QtFontFamily::Supported; + } + + FcCharSet *cs = 0; + res = FcPatternGetCharSet(fonts->fonts[i], FC_CHARSET, 0, &cs); + if (res == FcResultMatch) { + // some languages are not supported by FontConfig, we rather check the + // charset to detect these + for (int i = 1; i < SampleCharCount; ++i) { + if (!sampleCharForWritingSystem[i]) + continue; + if (FcCharSetHasChar(cs, sampleCharForWritingSystem[i])) + family->writingSystems[i] = QtFontFamily::Supported; + } + } + +#if FC_VERSION >= 20297 + for (int j = 1; j < LanguageCount; ++j) { + if (family->writingSystems[j] == QtFontFamily::Supported && requiresOpenType(j) && openType[j]) { + FcChar8 *cap; + res = FcPatternGetString (fonts->fonts[i], FC_CAPABILITY, 0, &cap); + if (res != FcResultMatch || !strstr((const char *)cap, openType[j])) + family->writingSystems[j] = QtFontFamily::UnsupportedFT; + } + } +#endif + + QByteArray file((const char *)file_value); + family->fontFilename = file; + family->fontFileIndex = index_value; + + QtFontStyle::Key styleKey; + styleKey.style = (slant_value == FC_SLANT_ITALIC) + ? QFont::StyleItalic + : ((slant_value == FC_SLANT_OBLIQUE) + ? QFont::StyleOblique + : QFont::StyleNormal); + styleKey.weight = getFCWeight(weight_value); + if (!scalable) { + int width = 100; + FcPatternGetInteger (fonts->fonts[i], FC_WIDTH, 0, &width); + styleKey.stretch = width; + } + + QtFontFoundry *foundry + = family->foundry(foundry_value ? QString::fromUtf8((const char *)foundry_value) : QString(), true); + QtFontStyle *style = foundry->style(styleKey, true); + + if (spacing_value < FC_MONO) + family->fixedPitch = false; + + QtFontSize *size; + if (scalable) { + style->smoothScalable = true; + size = style->pixelSize(SMOOTH_SCALABLE, true); + } else { + double pixel_size = 0; + FcPatternGetDouble (fonts->fonts[i], FC_PIXEL_SIZE, 0, &pixel_size); + size = style->pixelSize((int)pixel_size, true); + } + QtFontEncoding *enc = size->encodingID(-1, 0, 0, 0, 0, true); + enc->pitch = (spacing_value >= FC_CHARCELL ? 'c' : + (spacing_value >= FC_MONO ? 'm' : 'p')); + } + + FcFontSetDestroy (fonts); + + struct FcDefaultFont { + const char *qtname; + const char *rawname; + bool fixed; + }; + const FcDefaultFont defaults[] = { + { "Serif", "serif", false }, + { "Sans Serif", "sans-serif", false }, + { "Monospace", "monospace", true }, + { 0, 0, false } + }; + const FcDefaultFont *f = defaults; + while (f->qtname) { + QtFontFamily *family = db->family(QLatin1String(f->qtname), true); + family->fixedPitch = f->fixed; + family->synthetic = true; + QtFontFoundry *foundry = family->foundry(QString(), true); + + // aliases only make sense for 'common', not for any of the specials + for (int i = 1; i < LanguageCount; ++i) { + if (requiresOpenType(i)) + family->writingSystems[i] = QtFontFamily::UnsupportedFT; + else + family->writingSystems[i] = QtFontFamily::Supported; + } + family->writingSystems[QFontDatabase::Other] = QtFontFamily::UnsupportedFT; + + QtFontStyle::Key styleKey; + for (int i = 0; i < 4; ++i) { + styleKey.style = (i%2) ? QFont::StyleNormal : QFont::StyleItalic; + styleKey.weight = (i > 1) ? QFont::Bold : QFont::Normal; + QtFontStyle *style = foundry->style(styleKey, true); + style->smoothScalable = true; + QtFontSize *size = style->pixelSize(SMOOTH_SCALABLE, true); + QtFontEncoding *enc = size->encodingID(-1, 0, 0, 0, 0, true); + enc->pitch = (f->fixed ? 'm' : 'p'); + } + ++f; + } +} +#endif // QT_NO_FONTCONFIG + +static void initializeDb(); + +static void load(const QString &family = QString(), int script = -1, bool forceXLFD = false) +{ + if (X11->has_fontconfig && !forceXLFD) { + initializeDb(); + return; + } + +#ifdef QFONTDATABASE_DEBUG + QElapsedTimer t; + t.start(); +#endif + + if (family.isNull() && script == -1) { + loadXlfds(0, -1); + } else { + if (family.isNull()) { + // load all families in all writing systems that match \a script + for (int ws = 1; ws < QFontDatabase::WritingSystemsCount; ++ws) { + if (scriptForWritingSystem[ws] != script) + continue; + for (int i = 0; i < numEncodings; ++i) { + if (writingSystems_for_xlfd_encoding[i][ws]) + loadXlfds(0, i); + } + } + } else { + QtFontFamily *f = privateDb()->family(family); + // could reduce this further with some more magic: + // would need to remember the encodings loaded for the family. + if (!f || !f->xlfdLoaded) + loadXlfds(family.toLatin1(), -1); + } + } + +#ifdef QFONTDATABASE_DEBUG + FD_DEBUG("QFontDatabase: load(%s, %d) took %d ms", + family.toLatin1().constData(), script, t.elapsed()); +#endif +} + +static void checkSymbolFont(QtFontFamily *family) +{ + if (!family || family->symbol_checked || family->fontFilename.isEmpty()) + return; +// qDebug() << "checking " << family->rawName; + family->symbol_checked = true; + + QFontEngine::FaceId id; + id.filename = family->fontFilename; + id.index = family->fontFileIndex; + QFreetypeFace *f = QFreetypeFace::getFace(id); + if (!f) { + qWarning("checkSymbolFonts: Couldn't open face %s (%s/%d)", + qPrintable(family->name), family->fontFilename.data(), family->fontFileIndex); + return; + } + for (int i = 0; i < f->face->num_charmaps; ++i) { + FT_CharMap cm = f->face->charmaps[i]; + if (cm->encoding == FT_ENCODING_ADOBE_CUSTOM + || cm->encoding == FT_ENCODING_MS_SYMBOL) { + for (int x = QFontDatabase::Latin; x < QFontDatabase::Other; ++x) + family->writingSystems[x] = QtFontFamily::Unsupported; + family->writingSystems[QFontDatabase::Other] = QtFontFamily::Supported; + break; + } + } + f->release(id); +} + +static void checkSymbolFonts(const QString &family = QString()) +{ +#ifndef QT_NO_FONTCONFIG + QFontDatabasePrivate *d = privateDb(); + + if (family.isEmpty()) { + for (int i = 0; i < d->count; ++i) + checkSymbolFont(d->families[i]); + } else { + checkSymbolFont(d->family(family)); + } +#endif +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt); + +static void initializeDb() +{ + QFontDatabasePrivate *db = privateDb(); + if (!db || db->count) + return; + + QElapsedTimer t; + t.start(); + +#ifndef QT_NO_FONTCONFIG + if (db->reregisterAppFonts) { + db->reregisterAppFonts = false; + for (int i = 0; i < db->applicationFonts.count(); ++i) + if (!db->applicationFonts.at(i).families.isEmpty()) { + registerFont(&db->applicationFonts[i]); + } + } + + loadFontConfig(); + FD_DEBUG("QFontDatabase: loaded FontConfig: %d ms", int(t.elapsed())); +#endif + + t.start(); + +#ifndef QT_NO_FONTCONFIG + for (int i = 0; i < db->count; i++) { + for (int j = 0; j < db->families[i]->count; ++j) { // each foundry + QtFontFoundry *foundry = db->families[i]->foundries[j]; + for (int k = 0; k < foundry->count; ++k) { + QtFontStyle *style = foundry->styles[k]; + if (style->key.style != QFont::StyleNormal) continue; + + QtFontSize *size = style->pixelSize(SMOOTH_SCALABLE); + if (! size) continue; // should not happen + QtFontEncoding *enc = size->encodingID(-1, 0, 0, 0, 0, true); + if (! enc) continue; // should not happen either + + QtFontStyle::Key key = style->key; + + // does this style have an italic equivalent? + key.style = QFont::StyleItalic; + QtFontStyle *equiv = foundry->style(key); + if (equiv) continue; + + // does this style have an oblique equivalent? + key.style = QFont::StyleOblique; + equiv = foundry->style(key); + if (equiv) continue; + + // let's fake one... + equiv = foundry->style(key, true); + equiv->smoothScalable = true; + + QtFontSize *equiv_size = equiv->pixelSize(SMOOTH_SCALABLE, true); + QtFontEncoding *equiv_enc = equiv_size->encodingID(-1, 0, 0, 0, 0, true); + + // keep the same pitch + equiv_enc->pitch = enc->pitch; + } + } + } +#endif + + +#ifdef QFONTDATABASE_DEBUG +#ifndef QT_NO_FONTCONFIG + if (!X11->has_fontconfig) +#endif + // load everything at startup in debug mode. + loadXlfds(0, -1); + + // print the database + for (int f = 0; f < db->count; f++) { + QtFontFamily *family = db->families[f]; + FD_DEBUG("'%s' %s fixed=%s", family->name.latin1(), (family->fixedPitch ? "fixed" : ""), + (family->fixedPitch ? "yes" : "no")); + for (int i = 0; i < QFontDatabase::WritingSystemsCount; ++i) { + QFontDatabase::WritingSystem ws = QFontDatabase::WritingSystem(i); + FD_DEBUG("\t%s: %s", QFontDatabase::writingSystemName(ws).toLatin1().constData(), + ((family->writingSystems[i] & QtFontFamily::Supported) ? "Supported" : + (family->writingSystems[i] & QtFontFamily::Unsupported) == QtFontFamily::Unsupported ? + "Unsupported" : "Unknown")); + } + + for (int fd = 0; fd < family->count; fd++) { + QtFontFoundry *foundry = family->foundries[fd]; + FD_DEBUG("\t\t'%s'", foundry->name.latin1()); + for (int s = 0; s < foundry->count; s++) { + QtFontStyle *style = foundry->styles[s]; + FD_DEBUG("\t\t\tstyle: style=%d weight=%d (%s)\n" + "\t\t\tstretch=%d (%s)", + style->key.style, style->key.weight, + style->weightName, style->key.stretch, + style->setwidthName ? style->setwidthName : "nil"); + if (style->smoothScalable) + FD_DEBUG("\t\t\t\tsmooth scalable"); + else if (style->bitmapScalable) + FD_DEBUG("\t\t\t\tbitmap scalable"); + if (style->pixelSizes) { + qDebug("\t\t\t\t%d pixel sizes", style->count); + for (int z = 0; z < style->count; ++z) { + QtFontSize *size = style->pixelSizes + z; + for (int e = 0; e < size->count; ++e) { + FD_DEBUG("\t\t\t\t size %5d pitch %c encoding %s", + size->pixelSize, + size->encodings[e].pitch, + xlfd_for_id(size->encodings[e].encoding)); + } + } + } + } + } + } +#endif // QFONTDATABASE_DEBUG +} + + +// -------------------------------------------------------------------------------------- +// font loader +// -------------------------------------------------------------------------------------- + +static const char *styleHint(const QFontDef &request) +{ + const char *stylehint = 0; + switch (request.styleHint) { + case QFont::SansSerif: + stylehint = "sans-serif"; + break; + case QFont::Serif: + stylehint = "serif"; + break; + case QFont::TypeWriter: + stylehint = "monospace"; + break; + default: + if (request.fixedPitch) + stylehint = "monospace"; + break; + } + return stylehint; +} + +#ifndef QT_NO_FONTCONFIG + +void qt_addPatternProps(FcPattern *pattern, int screen, int script, const QFontDef &request) +{ + int weight_value = FC_WEIGHT_BLACK; + if (request.weight == 0) + weight_value = FC_WEIGHT_MEDIUM; + else if (request.weight < (QFont::Light + QFont::Normal) / 2) + weight_value = FC_WEIGHT_LIGHT; + else if (request.weight < (QFont::Normal + QFont::DemiBold) / 2) + weight_value = FC_WEIGHT_MEDIUM; + else if (request.weight < (QFont::DemiBold + QFont::Bold) / 2) + weight_value = FC_WEIGHT_DEMIBOLD; + else if (request.weight < (QFont::Bold + QFont::Black) / 2) + weight_value = FC_WEIGHT_BOLD; + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, weight_value); + + int slant_value = FC_SLANT_ROMAN; + if (request.style == QFont::StyleItalic) + slant_value = FC_SLANT_ITALIC; + else if (request.style == QFont::StyleOblique) + slant_value = FC_SLANT_OBLIQUE; + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, slant_value); + + double size_value = qMax(qreal(1.), request.pixelSize); + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size_value); + + int stretch = request.stretch; + if (!stretch) + stretch = 100; + FcPatternDel(pattern, FC_WIDTH); + FcPatternAddInteger(pattern, FC_WIDTH, stretch); + + if (X11->display && QX11Info::appDepth(screen) <= 8) { + FcPatternDel(pattern, FC_ANTIALIAS); + // can't do antialiasing on 8bpp + FcPatternAddBool(pattern, FC_ANTIALIAS, false); + } else if (request.styleStrategy & (QFont::PreferAntialias|QFont::NoAntialias)) { + FcPatternDel(pattern, FC_ANTIALIAS); + FcPatternAddBool(pattern, FC_ANTIALIAS, + !(request.styleStrategy & QFont::NoAntialias)); + } + + if (script != QUnicodeTables::Common && *specialLanguages[script] != '\0') { + Q_ASSERT(script < QUnicodeTables::ScriptCount); + FcLangSet *ls = FcLangSetCreate(); + FcLangSetAdd(ls, (const FcChar8*)specialLanguages[script]); + FcPatternDel(pattern, FC_LANG); + FcPatternAddLangSet(pattern, FC_LANG, ls); + FcLangSetDestroy(ls); + } +} + +static bool preferScalable(const QFontDef &request) +{ + return request.styleStrategy & (QFont::PreferOutline|QFont::ForceOutline|QFont::PreferQuality|QFont::PreferAntialias); +} + + +static FcPattern *getFcPattern(const QFontPrivate *fp, int script, const QFontDef &request) +{ + if (!X11->has_fontconfig) + return 0; + + FcPattern *pattern = FcPatternCreate(); + if (!pattern) + return 0; + + FcValue value; + value.type = FcTypeString; + + QtFontDesc desc; + QStringList families_and_foundries = familyList(request); + for (int i = 0; i < families_and_foundries.size(); ++i) { + QString family, foundry; + parseFontName(families_and_foundries.at(i), foundry, family); + if (!family.isEmpty()) { + QByteArray cs = family.toUtf8(); + value.u.s = (const FcChar8 *)cs.data(); + FcPatternAdd(pattern, FC_FAMILY, value, FcTrue); + } + if (i == 0) { + QT_PREPEND_NAMESPACE(match)(script, request, family, foundry, -1, &desc); + if (!foundry.isEmpty()) { + QByteArray cs = foundry.toUtf8(); + value.u.s = (const FcChar8 *)cs.data(); + FcPatternAddWeak(pattern, FC_FOUNDRY, value, FcTrue); + } + } + } + + const char *stylehint = styleHint(request); + if (stylehint) { + value.u.s = (const FcChar8 *)stylehint; + FcPatternAddWeak(pattern, FC_FAMILY, value, FcTrue); + } + + if (!request.ignorePitch) { + char pitch_value = FC_PROPORTIONAL; + if (request.fixedPitch || (desc.family && desc.family->fixedPitch)) + pitch_value = FC_MONO; + FcPatternAddInteger(pattern, FC_SPACING, pitch_value); + } + FcPatternAddBool(pattern, FC_OUTLINE, !(request.styleStrategy & QFont::PreferBitmap)); + if (preferScalable(request) || (desc.style && desc.style->smoothScalable)) + FcPatternAddBool(pattern, FC_SCALABLE, true); + + qt_addPatternProps(pattern, fp->screen, script, request); + + FcDefaultSubstitute(pattern); + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcConfigSubstitute(0, pattern, FcMatchFont); + + // these should only get added to the pattern _after_ substitution + // append the default fallback font for the specified script + extern QString qt_fallback_font_family(int); + QString fallback = qt_fallback_font_family(script); + if (!fallback.isEmpty()) { + QByteArray cs = fallback.toUtf8(); + value.u.s = (const FcChar8 *)cs.data(); + FcPatternAddWeak(pattern, FC_FAMILY, value, FcTrue); + } + + // add the default family + QString defaultFamily = QApplication::font().family(); + QByteArray cs = defaultFamily.toUtf8(); + value.u.s = (const FcChar8 *)cs.data(); + FcPatternAddWeak(pattern, FC_FAMILY, value, FcTrue); + + // add QFont::defaultFamily() to the list, for compatibility with + // previous versions + defaultFamily = QApplication::font().defaultFamily(); + cs = defaultFamily.toUtf8(); + value.u.s = (const FcChar8 *)cs.data(); + FcPatternAddWeak(pattern, FC_FAMILY, value, FcTrue); + + return pattern; +} + + +static void FcFontSetRemove(FcFontSet *fs, int at) +{ + Q_ASSERT(at < fs->nfont); + FcPatternDestroy(fs->fonts[at]); + int len = (--fs->nfont - at) * sizeof(FcPattern *);; + if (len > 0) + memmove(fs->fonts + at, fs->fonts + at + 1, len); +} + +static QFontEngine *tryPatternLoad(FcPattern *p, int screen, + const QFontDef &request, int script, FcPattern **matchedPattern = 0) +{ +#ifdef FONT_MATCH_DEBUG + FcChar8 *fam; + FcPatternGetString(p, FC_FAMILY, 0, &fam); + FM_DEBUG("==== trying %s\n", fam); +#endif + FM_DEBUG("passes charset test\n"); + FcPattern *pattern = FcPatternDuplicate(p); + // add properties back in as the font selected from the + // list doesn't contain them. + qt_addPatternProps(pattern, screen, script, request); + + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + FcResult res; + FcPattern *match = FcFontMatch(0, pattern, &res); + + if (matchedPattern) + *matchedPattern = 0; + + QFontEngineX11FT *engine = 0; + if (!match) // probably no fonts available. + goto done; + + if (matchedPattern) + *matchedPattern = FcPatternDuplicate(match); + + if (script != QUnicodeTables::Common) { + // skip font if it doesn't support the language we want + if (specialChars[script]) { + // need to check the charset, as the langset doesn't work for these scripts + FcCharSet *cs; + if (FcPatternGetCharSet(match, FC_CHARSET, 0, &cs) != FcResultMatch) + goto done; + if (!FcCharSetHasChar(cs, specialChars[script])) + goto done; + } else if (*specialLanguages[script] != '\0'){ + FcLangSet *langSet = 0; + if (FcPatternGetLangSet(match, FC_LANG, 0, &langSet) != FcResultMatch) + goto done; + if (FcLangSetHasLang(langSet, (const FcChar8*)specialLanguages[script]) != FcLangEqual) + goto done; + } + } + + // enforce non-antialiasing if requested. the ft font engine looks at this property. + if (request.styleStrategy & QFont::NoAntialias) { + FcPatternDel(match, FC_ANTIALIAS); + FcPatternAddBool(match, FC_ANTIALIAS, false); + } + + engine = new QFontEngineX11FT(match, qt_FcPatternToQFontDef(match, request), screen); + if (engine->invalid()) { + FM_DEBUG(" --> invalid!\n"); + delete engine; + engine = 0; + } else if (scriptRequiresOpenType(script)) { + HB_Face hbFace = engine->harfbuzzFace(); + if (!hbFace || !hbFace->supported_scripts[script]) { + FM_DEBUG(" OpenType support missing for script\n"); + delete engine; + engine = 0; + } + } +done: + FcPatternDestroy(pattern); + if (!engine && matchedPattern && *matchedPattern) { + FcPatternDestroy(*matchedPattern); + *matchedPattern = 0; + } + return engine; +} + +FcFontSet *qt_fontSetForPattern(FcPattern *pattern, const QFontDef &request) +{ + FcResult result; + FcFontSet *fs = FcFontSort(0, pattern, FcTrue, 0, &result); +#ifdef FONT_MATCH_DEBUG + FM_DEBUG("first font in fontset:\n"); + FcPatternPrint(fs->fonts[0]); +#endif + + FcBool forceScalable = request.styleStrategy & QFont::ForceOutline; + + // remove fonts if they are not scalable (and should be) + if (forceScalable && fs) { + for (int i = 0; i < fs->nfont; ++i) { + FcPattern *font = fs->fonts[i]; + FcResult res; + FcBool scalable; + res = FcPatternGetBool(font, FC_SCALABLE, 0, &scalable); + if (res != FcResultMatch || !scalable) { + FcFontSetRemove(fs, i); +#ifdef FONT_MATCH_DEBUG + FM_DEBUG("removing pattern:"); + FcPatternPrint(font); +#endif + --i; // go back one + } + } + } + + FM_DEBUG("final pattern contains %d fonts\n", fs->nfont); + + return fs; +} + +static QFontEngine *loadFc(const QFontPrivate *fp, int script, const QFontDef &request) +{ + FM_DEBUG("===================== loadFc: script=%d family='%s'\n", script, request.family.toLatin1().data()); + FcPattern *pattern = getFcPattern(fp, script, request); + +#ifdef FONT_MATCH_DEBUG + FM_DEBUG("\n\nfinal FcPattern contains:\n"); + FcPatternPrint(pattern); +#endif + + QFontEngine *fe = 0; + FcPattern *matchedPattern = 0; + fe = tryPatternLoad(pattern, fp->screen, request, script, &matchedPattern); + if (!fe) { + FcFontSet *fs = qt_fontSetForPattern(pattern, request); + + if (fs) { + for (int i = 0; !fe && i < fs->nfont; ++i) + fe = tryPatternLoad(fs->fonts[i], fp->screen, request, script, &matchedPattern); + FcFontSetDestroy(fs); + } + FM_DEBUG("engine for script %d is %s\n", script, fe ? fe->fontDef.family.toLatin1().data(): "(null)"); + } + if (fe + && script == QUnicodeTables::Common + && !(request.styleStrategy & QFont::NoFontMerging) && !fe->symbol) { + fe = new QFontEngineMultiFT(fe, matchedPattern, pattern, fp->screen, request); + } else { + FcPatternDestroy(pattern); + if (matchedPattern) + FcPatternDestroy(matchedPattern); + } + return fe; +} + +static FcPattern *queryFont(const FcChar8 *file, const QByteArray &data, int id, FcBlanks *blanks, int *count) +{ +#if FC_VERSION < 20402 + Q_UNUSED(data) + return FcFreeTypeQuery(file, id, blanks, count); +#else + if (data.isEmpty()) + return FcFreeTypeQuery(file, id, blanks, count); + + extern FT_Library qt_getFreetype(); + FT_Library lib = qt_getFreetype(); + + FcPattern *pattern = 0; + + FT_Face face; + if (!FT_New_Memory_Face(lib, (const FT_Byte *)data.constData(), data.size(), id, &face)) { + *count = face->num_faces; + + pattern = FcFreeTypeQueryFace(face, file, id, blanks); + + FT_Done_Face(face); + } + + return pattern; +#endif +} +#endif // QT_NO_FONTCONFIG + +static QFontEngine *loadRaw(const QFontPrivate *fp, const QFontDef &request) +{ + Q_ASSERT(fp && fp->rawMode); + + QByteArray xlfd = request.family.toLatin1(); + FM_DEBUG("Loading XLFD (rawmode) '%s'", xlfd.data()); + + QFontEngine *fe; + XFontStruct *xfs; + if (!(xfs = XLoadQueryFont(QX11Info::display(), xlfd.data()))) + if (!(xfs = XLoadQueryFont(QX11Info::display(), "fixed"))) + return 0; + + fe = new QFontEngineXLFD(xfs, xlfd, 0); + if (! qt_fillFontDef(xfs, &fe->fontDef, fp->dpi, 0) && + ! qt_fillFontDef(xlfd, &fe->fontDef, fp->dpi, 0)) + fe->fontDef = QFontDef(); + return fe; +} + +QFontEngine *QFontDatabase::loadXlfd(int screen, int script, const QFontDef &request, int force_encoding_id) +{ + QMutexLocker locker(fontDatabaseMutex()); + + QtFontDesc desc; + FM_DEBUG() << "---> loadXlfd: request is" << request.family; + QStringList families_and_foundries = familyList(request); + const char *stylehint = styleHint(request); + if (stylehint) + families_and_foundries << QString::fromLatin1(stylehint); + families_and_foundries << QString(); + FM_DEBUG() << "loadXlfd: list is" << families_and_foundries; + for (int i = 0; i < families_and_foundries.size(); ++i) { + QString family, foundry; + QT_PREPEND_NAMESPACE(parseFontName)(families_and_foundries.at(i), foundry, family); + FM_DEBUG("loadXlfd: >>>>>>>>>>>>>>trying to match '%s' encoding=%d", family.toLatin1().data(), force_encoding_id); + QT_PREPEND_NAMESPACE(match)(script, request, family, foundry, force_encoding_id, &desc, QList(), true); + if (desc.family) + break; + } + + QFontEngine *fe = 0; + if (force_encoding_id != -1 + || (request.styleStrategy & QFont::NoFontMerging) + || (desc.family && desc.family->writingSystems[QFontDatabase::Symbol] & QtFontFamily::Supported)) { + if (desc.family) { + int px = desc.size->pixelSize; + if (desc.style->smoothScalable && px == SMOOTH_SCALABLE) + px = request.pixelSize; + else if (desc.style->bitmapScalable && px == 0) + px = request.pixelSize; + + QByteArray xlfd("-"); + xlfd += desc.foundry->name.isEmpty() ? QByteArray("*") : desc.foundry->name.toLatin1(); + xlfd += '-'; + xlfd += desc.family->name.isEmpty() ? QByteArray("*") : desc.family->name.toLatin1(); + xlfd += '-'; + xlfd += desc.style->weightName ? desc.style->weightName : "*"; + xlfd += '-'; + xlfd += (desc.style->key.style == QFont::StyleItalic + ? 'i' + : (desc.style->key.style == QFont::StyleOblique ? 'o' : 'r')); + xlfd += '-'; + xlfd += desc.style->setwidthName ? desc.style->setwidthName : "*"; + // ### handle add-style + xlfd += "-*-"; + xlfd += QByteArray::number(px); + xlfd += '-'; + xlfd += QByteArray::number(desc.encoding->xpoint); + xlfd += '-'; + xlfd += QByteArray::number(desc.encoding->xres); + xlfd += '-'; + xlfd += QByteArray::number(desc.encoding->yres); + xlfd += '-'; + xlfd += desc.encoding->pitch; + xlfd += '-'; + xlfd += QByteArray::number(desc.encoding->avgwidth); + xlfd += '-'; + xlfd += xlfd_for_id(desc.encoding->encoding); + + FM_DEBUG(" using XLFD: %s\n", xlfd.data()); + + const int mib = xlfd_encoding[desc.encoding->encoding].mib; + XFontStruct *xfs; + if ((xfs = XLoadQueryFont(QX11Info::display(), xlfd))) { + fe = new QFontEngineXLFD(xfs, xlfd, mib); + const int dpi = QX11Info::appDpiY(); + if (!qt_fillFontDef(xfs, &fe->fontDef, dpi, &desc) + && !qt_fillFontDef(xlfd, &fe->fontDef, dpi, &desc)) { + initFontDef(desc, request, &fe->fontDef); + } + } + } + if (!fe) { + fe = new QFontEngineBox(request.pixelSize); + fe->fontDef = QFontDef(); + } + } else { + QList encodings; + if (desc.encoding) { + if (desc.encoding->encoding >= 0) + encodings.append(int(desc.encoding->encoding)); + } + + if (desc.size) { + // append all other encodings for the matched font + for (int i = 0; i < desc.size->count; ++i) { + QtFontEncoding *e = desc.size->encodings + i; + if (e == desc.encoding || e->encoding < 0) + continue; + encodings.append(int(e->encoding)); + } + } + // fill in the missing encodings + const XlfdEncoding *enc = xlfd_encoding; + for (; enc->name; ++enc) { + if (!encodings.contains(enc->id) && enc->id >= 0) { + encodings.append(enc->id); + } + } + +#if defined(FONT_MATCH_DEBUG) + FM_DEBUG(" using MultiXLFD, encodings:"); + for (int i = 0; i < encodings.size(); ++i) { + const int id = encodings.at(i); + FM_DEBUG(" %2d: %s", xlfd_encoding[id].id, xlfd_encoding[id].name); + } +#endif + + fe = new QFontEngineMultiXLFD(request, encodings, screen); + } + return fe; +} + +#if (defined(QT_ARCH_ARM) || defined(QT_ARCH_ARMV6)) && defined(Q_CC_GNU) && (__GNUC__ == 4) && (__GNUC_MINOR__ == 3) +#define NEEDS_GCC_BUG_WORKAROUND +#endif + +#ifdef NEEDS_GCC_BUG_WORKAROUND +static inline void gccBugWorkaround(const QFontDef &req) +{ + char buffer[8]; + snprintf(buffer, 8, "%f", req.pixelSize); +} +#endif + +/*! \internal + Loads a QFontEngine for the specified \a script that matches the + QFontDef \e request member variable. +*/ +void QFontDatabase::load(const QFontPrivate *d, int script) +{ + Q_ASSERT(script >= 0 && script < QUnicodeTables::ScriptCount); + + // normalize the request to get better caching + QFontDef req = d->request; + if (req.pixelSize <= 0) + req.pixelSize = qFloor(qt_pixelSize(req.pointSize, d->dpi) * 100.0 + 0.5) * 0.01; + if (req.pixelSize < 1) + req.pixelSize = 1; + +#ifdef NEEDS_GCC_BUG_WORKAROUND + // req.pixelSize ends up with a bogus value unless this workaround is called + gccBugWorkaround(req); +#endif + + if (req.weight == 0) + req.weight = QFont::Normal; + if (req.stretch == 0) + req.stretch = 100; + + QFontCache::Key key(req, d->rawMode ? QUnicodeTables::Common : script, d->screen); + if (!d->engineData) + getEngineData(d, key); + + // the cached engineData could have already loaded the engine we want + if (d->engineData->engines[script]) + return; + + // set it to the actual pointsize, so QFontInfo will do the right thing + if (req.pointSize < 0) + req.pointSize = qt_pointSize(req.pixelSize, d->dpi); + + + QFontEngine *fe = QFontCache::instance()->findEngine(key); + + if (!fe) { + QMutexLocker locker(fontDatabaseMutex()); + if (!privateDb()->count) + initializeDb(); + + const bool mainThread = (qApp->thread() == QThread::currentThread()); + if (qt_enable_test_font && req.family == QLatin1String("__Qt__Box__Engine__")) { + fe = new QTestFontEngine(req.pixelSize); + fe->fontDef = req; + } else if (d->rawMode) { + if (mainThread) + fe = loadRaw(d, req); +#ifndef QT_NO_FONTCONFIG + } else if (X11->has_fontconfig) { + fe = loadFc(d, script, req); +#endif + } else if (mainThread && qt_is_gui_used) { + fe = loadXlfd(d->screen, script, req); + } + if (!fe) { + fe = new QFontEngineBox(req.pixelSize); + fe->fontDef = QFontDef(); + } + } + if (fe->symbol || (d->request.styleStrategy & QFont::NoFontMerging)) { + for (int i = 0; i < QUnicodeTables::ScriptCount; ++i) { + if (!d->engineData->engines[i]) { + d->engineData->engines[i] = fe; + fe->ref.ref(); + } + } + } else { + d->engineData->engines[script] = fe; + fe->ref.ref(); + } + QFontCache::instance()->insertEngine(key, fe); +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt) +{ +#if defined(QT_NO_FONTCONFIG) + return; +#else + if (!X11->has_fontconfig) + return; + + FcConfig *config = FcConfigGetCurrent(); + if (!config) + return; + + FcFontSet *set = FcConfigGetFonts(config, FcSetApplication); + if (!set) { + FcConfigAppFontAddFile(config, (const FcChar8 *)":/non-existent"); + set = FcConfigGetFonts(config, FcSetApplication); // try again + if (!set) + return; + } + + QString fileNameForQuery = fnt->fileName; +#if FC_VERSION < 20402 + QTemporaryFile tmp; + + if (!fnt->data.isEmpty()) { + if (!tmp.open()) + return; + tmp.write(fnt->data); + tmp.flush(); + fileNameForQuery = tmp.fileName(); + } +#endif + + int id = 0; + FcBlanks *blanks = FcConfigGetBlanks(0); + int count = 0; + + QStringList families; + QFontDatabasePrivate *db = privateDb(); + + FcPattern *pattern = 0; + do { + pattern = queryFont((const FcChar8 *)QFile::encodeName(fileNameForQuery).constData(), + fnt->data, id, blanks, &count); + if (!pattern) + return; + + FcPatternDel(pattern, FC_FILE); + FcPatternAddString(pattern, FC_FILE, (const FcChar8 *)fnt->fileName.toUtf8().constData()); + + FcChar8 *fam = 0, *familylang = 0; + int i, n = 0; + for (i = 0; ; i++) { + if (FcPatternGetString(pattern, FC_FAMILYLANG, i, &familylang) != FcResultMatch) + break; + QString familyLang = QString::fromUtf8((const char *) familylang); + if (familyLang.compare(db->systemLang, Qt::CaseInsensitive) == 0) { + n = i; + break; + } + } + + if (FcPatternGetString(pattern, FC_FAMILY, n, &fam) == FcResultMatch) { + QString family = QString::fromUtf8(reinterpret_cast(fam)); + families << family; + } + + if (!FcFontSetAdd(set, pattern)) + return; + + ++id; + } while (pattern && id < count); + + fnt->families = families; +#endif +} + +bool QFontDatabase::removeApplicationFont(int handle) +{ +#if defined(QT_NO_FONTCONFIG) + return false; +#else + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (handle < 0 || handle >= db->applicationFonts.count()) + return false; + + FcConfigAppFontClear(0); + + db->applicationFonts[handle] = QFontDatabasePrivate::ApplicationFont(); + + db->reregisterAppFonts = true; + db->invalidate(); + return true; +#endif +} + +bool QFontDatabase::removeAllApplicationFonts() +{ +#if defined(QT_NO_FONTCONFIG) + return false; +#else + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if (db->applicationFonts.isEmpty()) + return false; + + FcConfigAppFontClear(0); + db->applicationFonts.clear(); + db->invalidate(); + return true; +#endif +} + +bool QFontDatabase::supportsThreadedFontRendering() +{ +#if defined(QT_NO_FONTCONFIG) + return false; +#else + return X11->has_fontconfig; +#endif +} + +QString QFontDatabase::resolveFontFamilyAlias(const QString &family) +{ +#if defined(QT_NO_FONTCONFIG) + return family; +#else + FcPattern *pattern = FcPatternCreate(); + if (!pattern) + return family; + + FcPatternAddString(pattern, FC_FAMILY, (const FcChar8 *) family.toUtf8().data()); + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + FcChar8 *familyAfterSubstitution; + FcPatternGetString(pattern, FC_FAMILY, 0, &familyAfterSubstitution); + QString resolved = QString::fromUtf8((const char *) familyAfterSubstitution); + FcPatternDestroy(pattern); + + return resolved; +#endif +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp new file mode 100644 index 0000000000..2f76cc615b --- /dev/null +++ b/src/gui/text/qfontengine.cpp @@ -0,0 +1,1684 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "qbitmap.h" +#include "qpainter.h" +#include "qpainterpath.h" +#include "qvarlengtharray.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static inline bool qtransform_equals_no_translate(const QTransform &a, const QTransform &b) +{ + if (a.type() <= QTransform::TxTranslate && b.type() <= QTransform::TxTranslate) { + return true; + } else { + // We always use paths for perspective text anyway, so no + // point in checking the full matrix... + Q_ASSERT(a.type() < QTransform::TxProject); + Q_ASSERT(b.type() < QTransform::TxProject); + + return a.m11() == b.m11() + && a.m12() == b.m12() + && a.m21() == b.m21() + && a.m22() == b.m22(); + } +} + +// Harfbuzz helper functions + +static HB_Bool hb_stringToGlyphs(HB_Font font, const HB_UChar16 *string, hb_uint32 length, HB_Glyph *glyphs, hb_uint32 *numGlyphs, HB_Bool rightToLeft) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + + QVarLengthGlyphLayoutArray qglyphs(*numGlyphs); + + QTextEngine::ShaperFlags shaperFlags(QTextEngine::GlyphIndicesOnly); + if (rightToLeft) + shaperFlags |= QTextEngine::RightToLeft; + + int nGlyphs = *numGlyphs; + bool result = fe->stringToCMap(reinterpret_cast(string), length, &qglyphs, &nGlyphs, shaperFlags); + *numGlyphs = nGlyphs; + if (!result) + return false; + + for (hb_uint32 i = 0; i < *numGlyphs; ++i) + glyphs[i] = qglyphs.glyphs[i]; + + return true; +} + +static void hb_getAdvances(HB_Font font, const HB_Glyph *glyphs, hb_uint32 numGlyphs, HB_Fixed *advances, int flags) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + + QVarLengthGlyphLayoutArray qglyphs(numGlyphs); + + for (hb_uint32 i = 0; i < numGlyphs; ++i) + qglyphs.glyphs[i] = glyphs[i]; + + fe->recalcAdvances(&qglyphs, flags & HB_ShaperFlag_UseDesignMetrics ? QFlags(QTextEngine::DesignMetrics) : QFlags(0)); + + for (hb_uint32 i = 0; i < numGlyphs; ++i) + advances[i] = qglyphs.advances_x[i].value(); +} + +static HB_Bool hb_canRender(HB_Font font, const HB_UChar16 *string, hb_uint32 length) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + return fe->canRender(reinterpret_cast(string), length); +} + +static void hb_getGlyphMetrics(HB_Font font, HB_Glyph glyph, HB_GlyphMetrics *metrics) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + glyph_metrics_t m = fe->boundingBox(glyph); + metrics->x = m.x.value(); + metrics->y = m.y.value(); + metrics->width = m.width.value(); + metrics->height = m.height.value(); + metrics->xOffset = m.xoff.value(); + metrics->yOffset = m.yoff.value(); +} + +static HB_Fixed hb_getFontMetric(HB_Font font, HB_FontMetric metric) +{ + if (metric == HB_FontAscent) { + QFontEngine *fe = (QFontEngine *)font->userData; + return fe->ascent().value(); + } + return 0; +} + +HB_Error QFontEngine::getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints) +{ + Q_UNUSED(glyph) + Q_UNUSED(flags) + Q_UNUSED(point) + Q_UNUSED(xpos) + Q_UNUSED(ypos) + Q_UNUSED(nPoints) + return HB_Err_Not_Covered; +} + +static HB_Error hb_getPointInOutline(HB_Font font, HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + return fe->getPointInOutline(glyph, flags, point, xpos, ypos, nPoints); +} + +static const HB_FontClass hb_fontClass = { + hb_stringToGlyphs, hb_getAdvances, hb_canRender, hb_getPointInOutline, + hb_getGlyphMetrics, hb_getFontMetric +}; + +static HB_Error hb_getSFntTable(void *font, HB_Tag tableTag, HB_Byte *buffer, HB_UInt *length) +{ + QFontEngine *fe = (QFontEngine *)font; + if (!fe->getSfntTableData(tableTag, buffer, length)) + return HB_Err_Invalid_Argument; + return HB_Err_Ok; +} + +// QFontEngine + +QFontEngine::QFontEngine() + : QObject() +{ + ref = 0; + cache_count = 0; + fsType = 0; + symbol = false; + memset(&hbFont, 0, sizeof(hbFont)); + hbFont.klass = &hb_fontClass; + hbFont.userData = this; + + hbFace = 0; + glyphFormat = -1; +} + +QFontEngine::~QFontEngine() +{ + m_glyphCaches.clear(); + qHBFreeFace(hbFace); +} + +QFixed QFontEngine::lineThickness() const +{ + // ad hoc algorithm + int score = fontDef.weight * fontDef.pixelSize; + int lw = score / 700; + + // looks better with thicker line for small pointsizes + if (lw < 2 && score >= 1050) lw = 2; + if (lw == 0) lw = 1; + + return lw; +} + +QFixed QFontEngine::underlinePosition() const +{ + return ((lineThickness() * 2) + 3) / 6; +} + +HB_Font QFontEngine::harfbuzzFont() const +{ + if (!hbFont.x_ppem) { + QFixed emSquare = emSquareSize(); + hbFont.x_ppem = fontDef.pixelSize; + hbFont.y_ppem = fontDef.pixelSize * fontDef.stretch / 100; + hbFont.x_scale = (QFixed(hbFont.x_ppem * (1 << 16)) / emSquare).value(); + hbFont.y_scale = (QFixed(hbFont.y_ppem * (1 << 16)) / emSquare).value(); + } + return &hbFont; +} + +HB_Face QFontEngine::harfbuzzFace() const +{ + if (!hbFace) { + hbFace = qHBNewFace(const_cast(this), hb_getSFntTable); + Q_CHECK_PTR(hbFace); + } + return hbFace; +} + +glyph_metrics_t QFontEngine::boundingBox(glyph_t glyph, const QTransform &matrix) +{ + glyph_metrics_t metrics = boundingBox(glyph); + + if (matrix.type() > QTransform::TxTranslate) { + return metrics.transformed(matrix); + } + return metrics; +} + +QFixed QFontEngine::xHeight() const +{ + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + QChar x((ushort)'x'); + stringToCMap(&x, 1, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly); + + glyph_metrics_t bb = const_cast(this)->boundingBox(glyphs.glyphs[0]); + return bb.height; +} + +QFixed QFontEngine::averageCharWidth() const +{ + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + QChar x((ushort)'x'); + stringToCMap(&x, 1, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly); + + glyph_metrics_t bb = const_cast(this)->boundingBox(glyphs.glyphs[0]); + return bb.xoff; +} + + +void QFontEngine::getGlyphPositions(const QGlyphLayout &glyphs, const QTransform &matrix, QTextItem::RenderFlags flags, + QVarLengthArray &glyphs_out, QVarLengthArray &positions) +{ + QFixed xpos; + QFixed ypos; + + const bool transform = matrix.m11() != 1. + || matrix.m12() != 0. + || matrix.m21() != 0. + || matrix.m22() != 1.; + if (!transform) { + xpos = QFixed::fromReal(matrix.dx()); + ypos = QFixed::fromReal(matrix.dy()); + } + + int current = 0; + if (flags & QTextItem::RightToLeft) { + int i = glyphs.numGlyphs; + int totalKashidas = 0; + while(i--) { + xpos += glyphs.advances_x[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + ypos += glyphs.advances_y[i]; + totalKashidas += glyphs.justifications[i].nKashidas; + } + positions.resize(glyphs.numGlyphs+totalKashidas); + glyphs_out.resize(glyphs.numGlyphs+totalKashidas); + + i = 0; + while(i < glyphs.numGlyphs) { + if (glyphs.attributes[i].dontPrint) { + ++i; + continue; + } + xpos -= glyphs.advances_x[i]; + ypos -= glyphs.advances_y[i]; + + QFixed gpos_x = xpos + glyphs.offsets[i].x; + QFixed gpos_y = ypos + glyphs.offsets[i].y; + if (transform) { + QPointF gpos(gpos_x.toReal(), gpos_y.toReal()); + gpos = gpos * matrix; + gpos_x = QFixed::fromReal(gpos.x()); + gpos_y = QFixed::fromReal(gpos.y()); + } + positions[current].x = gpos_x; + positions[current].y = gpos_y; + glyphs_out[current] = glyphs.glyphs[i]; + ++current; + if (glyphs.justifications[i].nKashidas) { + QChar ch(0x640); // Kashida character + QGlyphLayoutArray<8> g; + int nglyphs = 7; + stringToCMap(&ch, 1, &g, &nglyphs, 0); + for (uint k = 0; k < glyphs.justifications[i].nKashidas; ++k) { + xpos -= g.advances_x[0]; + ypos -= g.advances_y[0]; + + QFixed gpos_x = xpos + glyphs.offsets[i].x; + QFixed gpos_y = ypos + glyphs.offsets[i].y; + if (transform) { + QPointF gpos(gpos_x.toReal(), gpos_y.toReal()); + gpos = gpos * matrix; + gpos_x = QFixed::fromReal(gpos.x()); + gpos_y = QFixed::fromReal(gpos.y()); + } + positions[current].x = gpos_x; + positions[current].y = gpos_y; + glyphs_out[current] = g.glyphs[0]; + ++current; + } + } else { + xpos -= QFixed::fromFixed(glyphs.justifications[i].space_18d6); + } + ++i; + } + } else { + positions.resize(glyphs.numGlyphs); + glyphs_out.resize(glyphs.numGlyphs); + int i = 0; + if (!transform) { + while (i < glyphs.numGlyphs) { + if (!glyphs.attributes[i].dontPrint) { + positions[current].x = xpos + glyphs.offsets[i].x; + positions[current].y = ypos + glyphs.offsets[i].y; + glyphs_out[current] = glyphs.glyphs[i]; + xpos += glyphs.advances_x[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + ypos += glyphs.advances_y[i]; + ++current; + } + ++i; + } + } else { + while (i < glyphs.numGlyphs) { + if (!glyphs.attributes[i].dontPrint) { + QFixed gpos_x = xpos + glyphs.offsets[i].x; + QFixed gpos_y = ypos + glyphs.offsets[i].y; + QPointF gpos(gpos_x.toReal(), gpos_y.toReal()); + gpos = gpos * matrix; + positions[current].x = QFixed::fromReal(gpos.x()); + positions[current].y = QFixed::fromReal(gpos.y()); + glyphs_out[current] = glyphs.glyphs[i]; + xpos += glyphs.advances_x[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + ypos += glyphs.advances_y[i]; + ++current; + } + ++i; + } + } + } + positions.resize(current); + glyphs_out.resize(current); + Q_ASSERT(positions.size() == glyphs_out.size()); +} + +void QFontEngine::getGlyphBearings(glyph_t glyph, qreal *leftBearing, qreal *rightBearing) +{ + glyph_metrics_t gi = boundingBox(glyph); + bool isValid = gi.isValid(); + if (leftBearing != 0) + *leftBearing = isValid ? gi.x.toReal() : 0.0; + if (rightBearing != 0) + *rightBearing = isValid ? (gi.xoff - gi.x - gi.width).toReal() : 0.0; +} + +glyph_metrics_t QFontEngine::tightBoundingBox(const QGlyphLayout &glyphs) +{ + glyph_metrics_t overall; + + QFixed ymax = 0; + QFixed xmax = 0; + for (int i = 0; i < glyphs.numGlyphs; i++) { + glyph_metrics_t bb = boundingBox(glyphs.glyphs[i]); + QFixed x = overall.xoff + glyphs.offsets[i].x + bb.x; + QFixed y = overall.yoff + glyphs.offsets[i].y + bb.y; + overall.x = qMin(overall.x, x); + overall.y = qMin(overall.y, y); + xmax = qMax(xmax, x + bb.width); + ymax = qMax(ymax, y + bb.height); + overall.xoff += bb.xoff; + overall.yoff += bb.yoff; + } + overall.height = qMax(overall.height, ymax - overall.y); + overall.width = xmax - overall.x; + + return overall; +} + + +void QFontEngine::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, + QTextItem::RenderFlags flags) +{ + if (!glyphs.numGlyphs) + return; + + QVarLengthArray positions; + QVarLengthArray positioned_glyphs; + QTransform matrix = QTransform::fromTranslate(x, y); + getGlyphPositions(glyphs, matrix, flags, positioned_glyphs, positions); + addGlyphsToPath(positioned_glyphs.data(), positions.data(), positioned_glyphs.size(), path, flags); +} + +#define GRID(x, y) grid[(y)*(w+1) + (x)] +#define SET(x, y) (*(image_data + (y)*bpl + ((x) >> 3)) & (0x80 >> ((x) & 7))) + +enum { EdgeRight = 0x1, + EdgeDown = 0x2, + EdgeLeft = 0x4, + EdgeUp = 0x8 +}; + +static void collectSingleContour(qreal x0, qreal y0, uint *grid, int x, int y, int w, int h, QPainterPath *path) +{ + Q_UNUSED(h); + + path->moveTo(x + x0, y + y0); + while (GRID(x, y)) { + if (GRID(x, y) & EdgeRight) { + while (GRID(x, y) & EdgeRight) { + GRID(x, y) &= ~EdgeRight; + ++x; + } + Q_ASSERT(x <= w); + path->lineTo(x + x0, y + y0); + continue; + } + if (GRID(x, y) & EdgeDown) { + while (GRID(x, y) & EdgeDown) { + GRID(x, y) &= ~EdgeDown; + ++y; + } + Q_ASSERT(y <= h); + path->lineTo(x + x0, y + y0); + continue; + } + if (GRID(x, y) & EdgeLeft) { + while (GRID(x, y) & EdgeLeft) { + GRID(x, y) &= ~EdgeLeft; + --x; + } + Q_ASSERT(x >= 0); + path->lineTo(x + x0, y + y0); + continue; + } + if (GRID(x, y) & EdgeUp) { + while (GRID(x, y) & EdgeUp) { + GRID(x, y) &= ~EdgeUp; + --y; + } + Q_ASSERT(y >= 0); + path->lineTo(x + x0, y + y0); + continue; + } + } + path->closeSubpath(); +} + +Q_GUI_EXPORT void qt_addBitmapToPath(qreal x0, qreal y0, const uchar *image_data, int bpl, int w, int h, QPainterPath *path) +{ + uint *grid = new uint[(w+1)*(h+1)]; + // set up edges + for (int y = 0; y <= h; ++y) { + for (int x = 0; x <= w; ++x) { + bool topLeft = (x == 0)|(y == 0) ? false : SET(x - 1, y - 1); + bool topRight = (x == w)|(y == 0) ? false : SET(x, y - 1); + bool bottomLeft = (x == 0)|(y == h) ? false : SET(x - 1, y); + bool bottomRight = (x == w)|(y == h) ? false : SET(x, y); + + GRID(x, y) = 0; + if ((!topRight) & bottomRight) + GRID(x, y) |= EdgeRight; + if ((!bottomRight) & bottomLeft) + GRID(x, y) |= EdgeDown; + if ((!bottomLeft) & topLeft) + GRID(x, y) |= EdgeLeft; + if ((!topLeft) & topRight) + GRID(x, y) |= EdgeUp; + } + } + + // collect edges + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + if (!GRID(x, y)) + continue; + // found start of a contour, follow it + collectSingleContour(x0, y0, grid, x, y, w, h, path); + } + } + delete [] grid; +} + +#undef GRID +#undef SET + + +void QFontEngine::addBitmapFontToPath(qreal x, qreal y, const QGlyphLayout &glyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ +// TODO what to do with 'flags' ?? + Q_UNUSED(flags); + QFixed advanceX = QFixed::fromReal(x); + QFixed advanceY = QFixed::fromReal(y); + for (int i=0; i < glyphs.numGlyphs; ++i) { + glyph_metrics_t metrics = boundingBox(glyphs.glyphs[i]); + if (metrics.width.value() == 0 || metrics.height.value() == 0) { + advanceX += glyphs.advances_x[i]; + advanceY += glyphs.advances_y[i]; + continue; + } + const QImage alphaMask = alphaMapForGlyph(glyphs.glyphs[i]); + + const int w = alphaMask.width(); + const int h = alphaMask.height(); + const int srcBpl = alphaMask.bytesPerLine(); + QImage bitmap; + if (alphaMask.depth() == 1) { + bitmap = alphaMask; + } else { + bitmap = QImage(w, h, QImage::Format_Mono); + const uchar *imageData = alphaMask.bits(); + const int destBpl = bitmap.bytesPerLine(); + uchar *bitmapData = bitmap.bits(); + + for (int yi = 0; yi < h; ++yi) { + const uchar *src = imageData + yi*srcBpl; + uchar *dst = bitmapData + yi*destBpl; + for (int xi = 0; xi < w; ++xi) { + const int byte = xi / 8; + const int bit = xi % 8; + if (bit == 0) + dst[byte] = 0; + if (src[xi]) + dst[byte] |= 128 >> bit; + } + } + } + const uchar *bitmap_data = bitmap.bits(); + QFixedPoint offset = glyphs.offsets[i]; + advanceX += offset.x; + advanceY += offset.y; + qt_addBitmapToPath((advanceX + metrics.x).toReal(), (advanceY + metrics.y).toReal(), bitmap_data, bitmap.bytesPerLine(), w, h, path); + advanceX += glyphs.advances_x[i]; + advanceY += glyphs.advances_y[i]; + } +} + +void QFontEngine::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nGlyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ + qreal x = positions[0].x.toReal(); + qreal y = positions[0].y.toReal(); + QVarLengthGlyphLayoutArray g(nGlyphs); + + for (int i = 0; i < nGlyphs; ++i) { + g.glyphs[i] = glyphs[i]; + if (i < nGlyphs - 1) { + g.advances_x[i] = positions[i+1].x - positions[i].x; + g.advances_y[i] = positions[i+1].y - positions[i].y; + } else { + g.advances_x[i] = QFixed::fromReal(maxCharWidth()); + g.advances_y[i] = 0; + } + } + + addBitmapFontToPath(x, y, g, path, flags); +} + +QImage QFontEngine::alphaMapForGlyph(glyph_t glyph, QFixed /*subPixelPosition*/) +{ + // For font engines don't support subpixel positioning + return alphaMapForGlyph(glyph); +} + +QImage QFontEngine::alphaMapForGlyph(glyph_t glyph, const QTransform &t) +{ + QImage i = alphaMapForGlyph(glyph); + if (t.type() > QTransform::TxTranslate) + i = i.transformed(t).convertToFormat(QImage::Format_Indexed8); + Q_ASSERT(i.depth() <= 8); // To verify that transformed didn't change the format... + + return i; +} + +QImage QFontEngine::alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &t) +{ + if (! supportsSubPixelPositions()) + return alphaMapForGlyph(glyph, t); + + QImage i = alphaMapForGlyph(glyph, subPixelPosition); + if (t.type() > QTransform::TxTranslate) + i = i.transformed(t).convertToFormat(QImage::Format_Indexed8); + Q_ASSERT(i.depth() <= 8); // To verify that transformed didn't change the format... + + return i; +} + +QImage QFontEngine::alphaRGBMapForGlyph(glyph_t glyph, QFixed /*subPixelPosition*/, int /* margin */, const QTransform &t) +{ + QImage alphaMask = alphaMapForGlyph(glyph, t); + QImage rgbMask(alphaMask.width(), alphaMask.height(), QImage::Format_RGB32); + + QVector colorTable = alphaMask.colorTable(); + for (int y=0; y colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; y(table.data()), &len)) + return QByteArray(); + return table; +} + +void QFontEngine::setGlyphCache(void *key, QFontEngineGlyphCache *data) +{ + Q_ASSERT(data); + + GlyphCacheEntry entry; + entry.context = key; + entry.cache = data; + if (m_glyphCaches.contains(entry)) + return; + + // Limit the glyph caches to 4. This covers all 90 degree rotations and limits + // memory use when there is continuous or random rotation + if (m_glyphCaches.size() == 4) + m_glyphCaches.removeLast(); + + m_glyphCaches.push_front(entry); + +} + +QFontEngineGlyphCache *QFontEngine::glyphCache(void *key, QFontEngineGlyphCache::Type type, const QTransform &transform) const +{ + for (QLinkedList::const_iterator it = m_glyphCaches.constBegin(), end = m_glyphCaches.constEnd(); it != end; ++it) { + QFontEngineGlyphCache *c = it->cache.data(); + if (key == it->context + && type == c->cacheType() + && qtransform_equals_no_translate(c->m_transform, transform)) { + return c; + } + } + return 0; +} + +#if defined(Q_WS_WIN) || defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) +static inline QFixed kerning(int left, int right, const QFontEngine::KernPair *pairs, int numPairs) +{ + uint left_right = (left << 16) + right; + + left = 0, right = numPairs - 1; + while (left <= right) { + int middle = left + ( ( right - left ) >> 1 ); + + if(pairs[middle].left_right == left_right) + return pairs[middle].adjust; + + if (pairs[middle].left_right < left_right) + left = middle + 1; + else + right = middle - 1; + } + return 0; +} + +void QFontEngine::doKerning(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + int numPairs = kerning_pairs.size(); + if(!numPairs) + return; + + const KernPair *pairs = kerning_pairs.constData(); + + if(flags & QTextEngine::DesignMetrics) { + for(int i = 0; i < glyphs->numGlyphs - 1; ++i) + glyphs->advances_x[i] += kerning(glyphs->glyphs[i], glyphs->glyphs[i+1] , pairs, numPairs); + } else { + for(int i = 0; i < glyphs->numGlyphs - 1; ++i) + glyphs->advances_x[i] += qRound(kerning(glyphs->glyphs[i], glyphs->glyphs[i+1] , pairs, numPairs)); + } +} + +void QFontEngine::loadKerningPairs(QFixed scalingFactor) +{ + kerning_pairs.clear(); + + QByteArray tab = getSfntTable(MAKE_TAG('k', 'e', 'r', 'n')); + if (tab.isEmpty()) + return; + + const uchar *table = reinterpret_cast(tab.constData()); + + unsigned short version = qFromBigEndian(table); + if (version != 0) { +// qDebug("wrong version"); + return; + } + + unsigned short numTables = qFromBigEndian(table + 2); + { + int offset = 4; + for(int i = 0; i < numTables; ++i) { + if (offset + 6 > tab.size()) { +// qDebug("offset out of bounds"); + goto end; + } + const uchar *header = table + offset; + + ushort version = qFromBigEndian(header); + ushort length = qFromBigEndian(header+2); + ushort coverage = qFromBigEndian(header+4); +// qDebug("subtable: version=%d, coverage=%x",version, coverage); + if(version == 0 && coverage == 0x0001) { + if (offset + length > tab.size()) { +// qDebug("length ouf ot bounds"); + goto end; + } + const uchar *data = table + offset + 6; + + ushort nPairs = qFromBigEndian(data); + if(nPairs * 6 + 8 > length - 6) { +// qDebug("corrupt table!"); + // corrupt table + goto end; + } + + int off = 8; + for(int i = 0; i < nPairs; ++i) { + QFontEngine::KernPair p; + p.left_right = (((uint)qFromBigEndian(data+off)) << 16) + qFromBigEndian(data+off+2); + p.adjust = QFixed(((int)(short)qFromBigEndian(data+off+4))) / scalingFactor; + kerning_pairs.append(p); + off += 6; + } + } + offset += length; + } + } +end: + qSort(kerning_pairs); +// for (int i = 0; i < kerning_pairs.count(); ++i) +// qDebug() << 'i' << i << "left_right" << hex << kerning_pairs.at(i).left_right; +} + +#else +void QFontEngine::doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const +{ +} +#endif + +int QFontEngine::glyphCount() const +{ + QByteArray maxpTable = getSfntTable(MAKE_TAG('m', 'a', 'x', 'p')); + if (maxpTable.size() < 6) + return 0; + return qFromBigEndian(reinterpret_cast(maxpTable.constData() + 4)); +} + +const uchar *QFontEngine::getCMap(const uchar *table, uint tableSize, bool *isSymbolFont, int *cmapSize) +{ + const uchar *header = table; + if (tableSize < 4) + return 0; + + const uchar *endPtr = table + tableSize; + + // version check + if (qFromBigEndian(header) != 0) + return 0; + + unsigned short numTables = qFromBigEndian(header + 2); + const uchar *maps = table + 4; + if (maps + 8 * numTables > endPtr) + return 0; + + enum { + Invalid, + AppleRoman, + Symbol, + Unicode11, + Unicode, + MicrosoftUnicode, + MicrosoftUnicodeExtended + }; + + int symbolTable = -1; + int tableToUse = -1; + int score = Invalid; + for (int n = 0; n < numTables; ++n) { + const quint16 platformId = qFromBigEndian(maps + 8 * n); + const quint16 platformSpecificId = qFromBigEndian(maps + 8 * n + 2); + switch (platformId) { + case 0: // Unicode + if (score < Unicode && + (platformSpecificId == 0 || + platformSpecificId == 2 || + platformSpecificId == 3)) { + tableToUse = n; + score = Unicode; + } else if (score < Unicode11 && platformSpecificId == 1) { + tableToUse = n; + score = Unicode11; + } + break; + case 1: // Apple + if (score < AppleRoman && platformSpecificId == 0) { // Apple Roman + tableToUse = n; + score = AppleRoman; + } + break; + case 3: // Microsoft + switch (platformSpecificId) { + case 0: + symbolTable = n; + if (score < Symbol) { + tableToUse = n; + score = Symbol; + } + break; + case 1: + if (score < MicrosoftUnicode) { + tableToUse = n; + score = MicrosoftUnicode; + } + break; + case 0xa: + if (score < MicrosoftUnicodeExtended) { + tableToUse = n; + score = MicrosoftUnicodeExtended; + } + break; + default: + break; + } + default: + break; + } + } + if(tableToUse < 0) + return 0; + +resolveTable: + *isSymbolFont = (symbolTable > -1); + + unsigned int unicode_table = qFromBigEndian(maps + 8*tableToUse + 4); + + if (!unicode_table || unicode_table + 8 > tableSize) + return 0; + + // get the header of the unicode table + header = table + unicode_table; + + unsigned short format = qFromBigEndian(header); + unsigned int length; + if(format < 8) + length = qFromBigEndian(header + 2); + else + length = qFromBigEndian(header + 4); + + if (table + unicode_table + length > endPtr) + return 0; + *cmapSize = length; + + // To support symbol fonts that contain a unicode table for the symbol area + // we check the cmap tables and fall back to symbol font unless that would + // involve losing information from the unicode table + if (symbolTable > -1 && ((score == Unicode) || (score == Unicode11))) { + const uchar *selectedTable = table + unicode_table; + + // Check that none of the latin1 range are in the unicode table + bool unicodeTableHasLatin1 = false; + for (int uc=0x00; uc<0x100; ++uc) { + if (getTrueTypeGlyphIndex(selectedTable, uc) != 0) { + unicodeTableHasLatin1 = true; + break; + } + } + + // Check that at least one symbol char is in the unicode table + bool unicodeTableHasSymbols = false; + if (!unicodeTableHasLatin1) { + for (int uc=0xf000; uc<0xf100; ++uc) { + if (getTrueTypeGlyphIndex(selectedTable, uc) != 0) { + unicodeTableHasSymbols = true; + break; + } + } + } + + // Fall back to symbol table + if (!unicodeTableHasLatin1 && unicodeTableHasSymbols) { + tableToUse = symbolTable; + score = Symbol; + goto resolveTable; + } + } + + return table + unicode_table; +} + +quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, uint unicode) +{ + unsigned short format = qFromBigEndian(cmap); + if (format == 0) { + if (unicode < 256) + return (int) *(cmap+6+unicode); + } else if (format == 4) { + /* some fonts come with invalid cmap tables, where the last segment + specified end = start = rangeoffset = 0xffff, delta = 0x0001 + Since 0xffff is never a valid Unicode char anyway, we just get rid of the issue + by returning 0 for 0xffff + */ + if(unicode >= 0xffff) + return 0; + quint16 segCountX2 = qFromBigEndian(cmap + 6); + const unsigned char *ends = cmap + 14; + int i = 0; + for (; i < segCountX2/2 && qFromBigEndian(ends + 2*i) < unicode; i++) {} + + const unsigned char *idx = ends + segCountX2 + 2 + 2*i; + quint16 startIndex = qFromBigEndian(idx); + + if (startIndex > unicode) + return 0; + + idx += segCountX2; + qint16 idDelta = (qint16)qFromBigEndian(idx); + idx += segCountX2; + quint16 idRangeoffset_t = (quint16)qFromBigEndian(idx); + + quint16 glyphIndex; + if (idRangeoffset_t) { + quint16 id = qFromBigEndian(idRangeoffset_t + 2*(unicode - startIndex) + idx); + if (id) + glyphIndex = (idDelta + id) % 0x10000; + else + glyphIndex = 0; + } else { + glyphIndex = (idDelta + unicode) % 0x10000; + } + return glyphIndex; + } else if (format == 6) { + quint16 tableSize = qFromBigEndian(cmap + 2); + + quint16 firstCode6 = qFromBigEndian(cmap + 6); + if (unicode < firstCode6) + return 0; + + quint16 entryCount6 = qFromBigEndian(cmap + 8); + if (entryCount6 * 2 + 10 > tableSize) + return 0; + + quint16 sentinel6 = firstCode6 + entryCount6; + if (unicode >= sentinel6) + return 0; + + quint16 entryIndex6 = unicode - firstCode6; + return qFromBigEndian(cmap + 10 + (entryIndex6 * 2)); + } else if (format == 12) { + quint32 nGroups = qFromBigEndian(cmap + 12); + + cmap += 16; // move to start of groups + + int left = 0, right = nGroups - 1; + while (left <= right) { + int middle = left + ( ( right - left ) >> 1 ); + + quint32 startCharCode = qFromBigEndian(cmap + 12*middle); + if(unicode < startCharCode) + right = middle - 1; + else { + quint32 endCharCode = qFromBigEndian(cmap + 12*middle + 4); + if(unicode <= endCharCode) + return qFromBigEndian(cmap + 12*middle + 8) + unicode - startCharCode; + left = middle + 1; + } + } + } else { + qDebug("cmap table of format %d not implemented", format); + } + + return 0; +} + +QByteArray QFontEngine::convertToPostscriptFontFamilyName(const QByteArray &family) +{ + QByteArray f = family; + f.replace(' ', ""); + f.replace('(', ""); + f.replace(')', ""); + f.replace('<', ""); + f.replace('>', ""); + f.replace('[', ""); + f.replace(']', ""); + f.replace('{', ""); + f.replace('}', ""); + f.replace('/', ""); + f.replace('%', ""); + return f; +} + +Q_GLOBAL_STATIC_WITH_INITIALIZER(QVector, qt_grayPalette, { + x->resize(256); + QRgb *it = x->data(); + for (int i = 0; i < x->size(); ++i, ++it) + *it = 0xff000000 | i | (i<<8) | (i<<16); +}) + +const QVector &QFontEngine::grayPalette() +{ + return *qt_grayPalette(); +} + +QFixed QFontEngine::lastRightBearing(const QGlyphLayout &glyphs, bool round) +{ + if (glyphs.numGlyphs >= 1) { + glyph_t glyph = glyphs.glyphs[glyphs.numGlyphs - 1]; + glyph_metrics_t gi = boundingBox(glyph); + if (gi.isValid()) + return round ? QFixed(qRound(gi.xoff - gi.x - gi.width)) + : QFixed(gi.xoff - gi.x - gi.width); + } + return 0; +} + +// ------------------------------------------------------------------ +// The box font engine +// ------------------------------------------------------------------ + +QFontEngineBox::QFontEngineBox(int size) + : _size(size) +{ + cache_cost = sizeof(QFontEngineBox); +} + +QFontEngineBox::~QFontEngineBox() +{ +} + +bool QFontEngineBox::stringToCMap(const QChar *, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + + for (int i = 0; i < len; i++) { + glyphs->glyphs[i] = 0; + glyphs->advances_x[i] = _size; + glyphs->advances_y[i] = 0; + } + + *nglyphs = len; + glyphs->numGlyphs = len; + return true; +} + +void QFontEngineBox::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const +{ + for (int i = 0; i < glyphs->numGlyphs; i++) { + glyphs->advances_x[i] = _size; + glyphs->advances_y[i] = 0; + } +} + +void QFontEngineBox::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (!glyphs.numGlyphs) + return; + + QVarLengthArray positions; + QVarLengthArray positioned_glyphs; + QTransform matrix = QTransform::fromTranslate(x, y - _size); + getGlyphPositions(glyphs, matrix, flags, positioned_glyphs, positions); + + QSize s(_size - 3, _size - 3); + for (int k = 0; k < positions.size(); k++) + path->addRect(QRectF(positions[k].toPointF(), s)); +} + +glyph_metrics_t QFontEngineBox::boundingBox(const QGlyphLayout &glyphs) +{ + glyph_metrics_t overall; + overall.width = _size*glyphs.numGlyphs; + overall.height = _size; + overall.xoff = overall.width; + return overall; +} + +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) +void QFontEngineBox::draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &ti) +{ + if (!ti.glyphs.numGlyphs) + return; + + // any fixes here should probably also be done in QPaintEnginePrivate::drawBoxTextItem + QSize s(_size - 3, _size - 3); + + QVarLengthArray positions; + QVarLengthArray glyphs; + QTransform matrix = QTransform::fromTranslate(x, y - _size); + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + + QPainter *painter = p->painter(); + painter->save(); + painter->setBrush(Qt::NoBrush); + QPen pen = painter->pen(); + pen.setWidthF(lineThickness().toReal()); + painter->setPen(pen); + for (int k = 0; k < positions.size(); k++) + painter->drawRect(QRectF(positions[k].toPointF(), s)); + painter->restore(); +} +#endif + +glyph_metrics_t QFontEngineBox::boundingBox(glyph_t) +{ + return glyph_metrics_t(0, -_size, _size, _size, _size, 0); +} + + + +QFixed QFontEngineBox::ascent() const +{ + return _size; +} + +QFixed QFontEngineBox::descent() const +{ + return 0; +} + +QFixed QFontEngineBox::leading() const +{ + QFixed l = _size * QFixed::fromReal(qreal(0.15)); + return l.ceil(); +} + +qreal QFontEngineBox::maxCharWidth() const +{ + return _size; +} + +#ifdef Q_WS_X11 +int QFontEngineBox::cmap() const +{ + return -1; +} +#endif + +const char *QFontEngineBox::name() const +{ + return "null"; +} + +bool QFontEngineBox::canRender(const QChar *, int) +{ + return true; +} + +QFontEngine::Type QFontEngineBox::type() const +{ + return Box; +} + +QImage QFontEngineBox::alphaMapForGlyph(glyph_t) +{ + QImage image(_size, _size, QImage::Format_Indexed8); + QVector colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + image.setColorTable(colors); + image.fill(0); + + // can't use qpainter for index8; so use setPixel to draw our rectangle. + for (int i=2; i <= _size-3; ++i) { + image.setPixel(i, 2, 255); + image.setPixel(i, _size-3, 255); + image.setPixel(2, i, 255); + image.setPixel(_size-3, i, 255); + } + return image; +} + +// ------------------------------------------------------------------ +// Multi engine +// ------------------------------------------------------------------ + +static inline uchar highByte(glyph_t glyph) +{ return glyph >> 24; } + +// strip high byte from glyph +static inline glyph_t stripped(glyph_t glyph) +{ return glyph & 0x00ffffff; } + +QFontEngineMulti::QFontEngineMulti(int engineCount) +{ + engines.fill(0, engineCount); + cache_cost = 0; +} + +QFontEngineMulti::~QFontEngineMulti() +{ + for (int i = 0; i < engines.size(); ++i) { + QFontEngine *fontEngine = engines.at(i); + if (fontEngine) { + fontEngine->ref.deref(); + if (fontEngine->cache_count == 0 && fontEngine->ref == 0) + delete fontEngine; + } + } +} + +bool QFontEngineMulti::stringToCMap(const QChar *str, int len, + QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags) const +{ + int ng = *nglyphs; + if (!engine(0)->stringToCMap(str, len, glyphs, &ng, flags)) + return false; + + int glyph_pos = 0; + for (int i = 0; i < len; ++i) { + bool surrogate = (str[i].unicode() >= 0xd800 && str[i].unicode() < 0xdc00 && i < len-1 + && str[i+1].unicode() >= 0xdc00 && str[i+1].unicode() < 0xe000); + + if (glyphs->glyphs[glyph_pos] == 0 && str[i].category() != QChar::Separator_Line) { + QGlyphLayoutInstance tmp = glyphs->instance(glyph_pos); + for (int x = 1; x < engines.size(); ++x) { + QFontEngine *engine = engines.at(x); + if (!engine) { + const_cast(this)->loadEngine(x); + engine = engines.at(x); + } + Q_ASSERT(engine != 0); + if (engine->type() == Box) + continue; + glyphs->advances_x[glyph_pos] = glyphs->advances_y[glyph_pos] = 0; + glyphs->offsets[glyph_pos] = QFixedPoint(); + int num = 2; + QGlyphLayout offs = glyphs->mid(glyph_pos, num); + engine->stringToCMap(str + i, surrogate ? 2 : 1, &offs, &num, flags); + Q_ASSERT(num == 1); // surrogates only give 1 glyph + if (glyphs->glyphs[glyph_pos]) { + // set the high byte to indicate which engine the glyph came from + glyphs->glyphs[glyph_pos] |= (x << 24); + break; + } + } + // ensure we use metrics from the 1st font when we use the fallback image. + if (!glyphs->glyphs[glyph_pos]) { + glyphs->setInstance(glyph_pos, tmp); + } + } + if (surrogate) + ++i; + ++glyph_pos; + } + + *nglyphs = ng; + glyphs->numGlyphs = ng; + return true; +} + +glyph_metrics_t QFontEngineMulti::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs <= 0) + return glyph_metrics_t(); + + glyph_metrics_t overall; + + int which = highByte(glyphs.glyphs[0]); + int start = 0; + int end, i; + for (end = 0; end < glyphs.numGlyphs; ++end) { + const int e = highByte(glyphs.glyphs[end]); + if (e == which) + continue; + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + + // merge the bounding box for this run + const glyph_metrics_t gm = engine(which)->boundingBox(glyphs.mid(start, end - start)); + + overall.x = qMin(overall.x, gm.x); + overall.y = qMin(overall.y, gm.y); + overall.width = overall.xoff + gm.width; + overall.height = qMax(overall.height + overall.y, gm.height + gm.y) - + qMin(overall.y, gm.y); + overall.xoff += gm.xoff; + overall.yoff += gm.yoff; + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + + // change engine + start = end; + which = e; + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + + // merge the bounding box for this run + const glyph_metrics_t gm = engine(which)->boundingBox(glyphs.mid(start, end - start)); + + overall.x = qMin(overall.x, gm.x); + overall.y = qMin(overall.y, gm.y); + overall.width = overall.xoff + gm.width; + overall.height = qMax(overall.height + overall.y, gm.height + gm.y) - + qMin(overall.y, gm.y); + overall.xoff += gm.xoff; + overall.yoff += gm.yoff; + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + + return overall; +} + +void QFontEngineMulti::getGlyphBearings(glyph_t glyph, qreal *leftBearing, qreal *rightBearing) +{ + int which = highByte(glyph); + engine(which)->getGlyphBearings(stripped(glyph), leftBearing, rightBearing); +} + +void QFontEngineMulti::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (glyphs.numGlyphs <= 0) + return; + + int which = highByte(glyphs.glyphs[0]); + int start = 0; + int end, i; + if (flags & QTextItem::RightToLeft) { + for (int gl = 0; gl < glyphs.numGlyphs; gl++) { + x += glyphs.advances_x[gl].toReal(); + y += glyphs.advances_y[gl].toReal(); + } + } + for (end = 0; end < glyphs.numGlyphs; ++end) { + const int e = highByte(glyphs.glyphs[end]); + if (e == which) + continue; + + if (flags & QTextItem::RightToLeft) { + for (i = start; i < end; ++i) { + x -= glyphs.advances_x[i].toReal(); + y -= glyphs.advances_y[i].toReal(); + } + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + engine(which)->addOutlineToPath(x, y, glyphs.mid(start, end - start), path, flags); + // reset the high byte for all glyphs and update x and y + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + + if (!(flags & QTextItem::RightToLeft)) { + for (i = start; i < end; ++i) { + x += glyphs.advances_x[i].toReal(); + y += glyphs.advances_y[i].toReal(); + } + } + + // change engine + start = end; + which = e; + } + + if (flags & QTextItem::RightToLeft) { + for (i = start; i < end; ++i) { + x -= glyphs.advances_x[i].toReal(); + y -= glyphs.advances_y[i].toReal(); + } + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + + engine(which)->addOutlineToPath(x, y, glyphs.mid(start, end - start), path, flags); + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; +} + +void QFontEngineMulti::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + if (glyphs->numGlyphs <= 0) + return; + + int which = highByte(glyphs->glyphs[0]); + int start = 0; + int end, i; + for (end = 0; end < glyphs->numGlyphs; ++end) { + const int e = highByte(glyphs->glyphs[end]); + if (e == which) + continue; + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->recalcAdvances(&offs, flags); + + // reset the high byte for all glyphs and update x and y + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; + + // change engine + start = end; + which = e; + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->recalcAdvances(&offs, flags); + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; +} + +void QFontEngineMulti::doKerning(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + if (glyphs->numGlyphs <= 0) + return; + + int which = highByte(glyphs->glyphs[0]); + int start = 0; + int end, i; + for (end = 0; end < glyphs->numGlyphs; ++end) { + const int e = highByte(glyphs->glyphs[end]); + if (e == which) + continue; + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->doKerning(&offs, flags); + + // reset the high byte for all glyphs and update x and y + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; + + // change engine + start = end; + which = e; + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->doKerning(&offs, flags); + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; +} + +glyph_metrics_t QFontEngineMulti::boundingBox(glyph_t glyph) +{ + const int which = highByte(glyph); + Q_ASSERT(which < engines.size()); + return engine(which)->boundingBox(stripped(glyph)); +} + +QFixed QFontEngineMulti::ascent() const +{ return engine(0)->ascent(); } + +QFixed QFontEngineMulti::descent() const +{ return engine(0)->descent(); } + +QFixed QFontEngineMulti::leading() const +{ + return engine(0)->leading(); +} + +QFixed QFontEngineMulti::xHeight() const +{ + return engine(0)->xHeight(); +} + +QFixed QFontEngineMulti::averageCharWidth() const +{ + return engine(0)->averageCharWidth(); +} + +QFixed QFontEngineMulti::lineThickness() const +{ + return engine(0)->lineThickness(); +} + +QFixed QFontEngineMulti::underlinePosition() const +{ + return engine(0)->underlinePosition(); +} + +qreal QFontEngineMulti::maxCharWidth() const +{ + return engine(0)->maxCharWidth(); +} + +qreal QFontEngineMulti::minLeftBearing() const +{ + return engine(0)->minLeftBearing(); +} + +qreal QFontEngineMulti::minRightBearing() const +{ + return engine(0)->minRightBearing(); +} + +bool QFontEngineMulti::canRender(const QChar *string, int len) +{ + if (engine(0)->canRender(string, len)) + return true; + + QVarLengthGlyphLayoutArray glyphs(len); + int nglyphs = len; + if (stringToCMap(string, len, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly) == false) { + glyphs.resize(nglyphs); + stringToCMap(string, len, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly); + } + + bool allExist = true; + for (int i = 0; i < nglyphs; i++) { + if (!glyphs.glyphs[i]) { + allExist = false; + break; + } + } + + return allExist; +} + +QImage QFontEngineMulti::alphaMapForGlyph(glyph_t) +{ + Q_ASSERT(false); + return QImage(); +} + + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_coretext.mm b/src/gui/text/qfontengine_coretext.mm new file mode 100644 index 0000000000..20b37300fa --- /dev/null +++ b/src/gui/text/qfontengine_coretext.mm @@ -0,0 +1,843 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontengine_coretext_p.h" + +#include +#include + +#include + +#if !defined(Q_WS_MAC) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + +QT_BEGIN_NAMESPACE + +static float SYNTHETIC_ITALIC_SKEW = tanf(14 * acosf(0) / 90); + +static void loadAdvancesForGlyphs(CTFontRef ctfont, + QVarLengthArray &cgGlyphs, + QGlyphLayout *glyphs, int len, + QTextEngine::ShaperFlags flags, + const QFontDef &fontDef) +{ + Q_UNUSED(flags); + QVarLengthArray advances(len); + CTFontGetAdvancesForGlyphs(ctfont, kCTFontHorizontalOrientation, cgGlyphs.data(), advances.data(), len); + + for (int i = 0; i < len; ++i) { + if (glyphs->glyphs[i] & 0xff000000) + continue; + glyphs->advances_x[i] = QFixed::fromReal(advances[i].width); + glyphs->advances_y[i] = QFixed::fromReal(advances[i].height); + } + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + for (int i = 0; i < len; ++i) { + glyphs->advances_x[i] = glyphs->advances_x[i].round(); + glyphs->advances_y[i] = glyphs->advances_y[i].round(); + } + } +} + +QCoreTextFontEngineMulti::QCoreTextFontEngineMulti(const QCFString &name, const QFontDef &fontDef, bool kerning) + : QFontEngineMulti(0) +{ + this->fontDef = fontDef; + CTFontSymbolicTraits symbolicTraits = 0; + if (fontDef.weight >= QFont::Bold) + symbolicTraits |= kCTFontBoldTrait; + switch (fontDef.style) { + case QFont::StyleNormal: + break; + case QFont::StyleItalic: + case QFont::StyleOblique: + symbolicTraits |= kCTFontItalicTrait; + break; + } + + transform = CGAffineTransformIdentity; + if (fontDef.stretch != 100) { + transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1); + } + + QCFType descriptor = CTFontDescriptorCreateWithNameAndSize(name, fontDef.pixelSize); + QCFType baseFont = CTFontCreateWithFontDescriptor(descriptor, fontDef.pixelSize, &transform); + ctfont = CTFontCreateCopyWithSymbolicTraits(baseFont, fontDef.pixelSize, &transform, symbolicTraits, symbolicTraits); + + // CTFontCreateCopyWithSymbolicTraits returns NULL if we ask for a trait that does + // not exist for the given font. (for example italic) + if (ctfont == 0) { + ctfont = baseFont; + CFRetain(ctfont); + } + init(kerning); +} + +QCoreTextFontEngineMulti::QCoreTextFontEngineMulti(CGFontRef cgFontRef, const QFontDef &fontDef, bool kerning) + : QFontEngineMulti(0) +{ + this->fontDef = fontDef; + + transform = CGAffineTransformIdentity; + if (fontDef.stretch != 100) { + transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1); + } + + ctfont = CTFontCreateWithGraphicsFont(cgFontRef, fontDef.pixelSize, &transform, NULL); + init(kerning); +} + +QCoreTextFontEngineMulti::~QCoreTextFontEngineMulti() +{ + CFRelease(ctfont); +} + +void QCoreTextFontEngineMulti::init(bool kerning) +{ + Q_ASSERT(ctfont != NULL); + attributeDict = CFDictionaryCreateMutable(0, 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(attributeDict, NSFontAttributeName, ctfont); + if (!kerning) { + float zero = 0.0; + QCFType noKern = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &zero); + CFDictionaryAddValue(attributeDict, kCTKernAttributeName, noKern); + } + + QCoreTextFontEngine *fe = new QCoreTextFontEngine(ctfont, fontDef); + fe->ref.ref(); + engines.append(fe); +} + +uint QCoreTextFontEngineMulti::fontIndexForFont(CTFontRef font) const +{ + for (int i = 0; i < engines.count(); ++i) { + if (CFEqual(engineAt(i)->ctfont, font)) + return i; + } + + QCoreTextFontEngineMulti *that = const_cast(this); + QCoreTextFontEngine *fe = new QCoreTextFontEngine(font, fontDef); + fe->ref.ref(); + that->engines.append(fe); + return engines.count() - 1; +} + +bool QCoreTextFontEngineMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags, + unsigned short *logClusters, const HB_CharAttributes *) const +{ + QCFType cfstring = CFStringCreateWithCharactersNoCopy(0, + reinterpret_cast(str), + len, kCFAllocatorNull); + QCFType attributedString = CFAttributedStringCreate(0, cfstring, attributeDict); + QCFType typeSetter; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + if (flags & QTextEngine::RightToLeft) { + const void *optionKeys[] = { kCTTypesetterOptionForcedEmbeddingLevel }; + const short rtlForcedEmbeddingLevelValue = 1; + const void *rtlOptionValues[] = { CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &rtlForcedEmbeddingLevelValue) }; + QCFType options = CFDictionaryCreate(kCFAllocatorDefault, optionKeys, rtlOptionValues, 1, + &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + typeSetter = CTTypesetterCreateWithAttributedStringAndOptions(attributedString, options); + } else +#endif + typeSetter = CTTypesetterCreateWithAttributedString(attributedString); + + CFRange range = {0, 0}; + QCFType line = CTTypesetterCreateLine(typeSetter, range); + CFArrayRef array = CTLineGetGlyphRuns(line); + uint arraySize = CFArrayGetCount(array); + glyph_t *outGlyphs = glyphs->glyphs; + HB_GlyphAttributes *outAttributes = glyphs->attributes; + QFixed *outAdvances_x = glyphs->advances_x; + QFixed *outAdvances_y = glyphs->advances_y; + glyph_t *initialGlyph = outGlyphs; + + if (arraySize == 0) { + // CoreText failed to shape the text we gave it, so we assume one glyph + // per character and build a list of invalid glyphs with zero advance + *nglyphs = len; + for (int i = 0; i < len; ++i) { + outGlyphs[i] = 0; + if (logClusters) + logClusters[i] = i; + outAdvances_x[i] = QFixed(); + outAdvances_y[i] = QFixed(); + outAttributes[i].clusterStart = true; + } + return true; + } + + const bool rtl = (CTRunGetStatus(static_cast(CFArrayGetValueAtIndex(array, 0))) & kCTRunStatusRightToLeft); + + bool outOBounds = false; + for (uint i = 0; i < arraySize; ++i) { + CTRunRef run = static_cast(CFArrayGetValueAtIndex(array, rtl ? (arraySize - 1 - i) : i)); + CFIndex glyphCount = CTRunGetGlyphCount(run); + if (glyphCount == 0) + continue; + + Q_ASSERT((CTRunGetStatus(run) & kCTRunStatusRightToLeft) == rtl); + CFRange stringRange = CTRunGetStringRange(run); + UniChar endGlyph = CFStringGetCharacterAtIndex(cfstring, stringRange.location + stringRange.length - 1); + bool endWithPDF = QChar::direction(endGlyph) == QChar::DirPDF; + if (endWithPDF) + glyphCount++; + + if (!outOBounds && outGlyphs + glyphCount - initialGlyph > *nglyphs) { + outOBounds = true; + } + if (!outOBounds) { + CFDictionaryRef runAttribs = CTRunGetAttributes(run); + //NSLog(@"Dictionary %@", runAttribs); + if (!runAttribs) + runAttribs = attributeDict; + CTFontRef runFont = static_cast(CFDictionaryGetValue(runAttribs, NSFontAttributeName)); + const uint fontIndex = (fontIndexForFont(runFont) << 24); + //NSLog(@"Run Font Name = %@", CTFontCopyFamilyName(runFont)); + if (endWithPDF) + glyphCount--; + + QVarLengthArray cgglyphs(0); + const CGGlyph *tmpGlyphs = CTRunGetGlyphsPtr(run); + if (!tmpGlyphs) { + cgglyphs.resize(glyphCount); + CTRunGetGlyphs(run, range, cgglyphs.data()); + tmpGlyphs = cgglyphs.constData(); + } + QVarLengthArray cgpoints(0); + const CGPoint *tmpPoints = CTRunGetPositionsPtr(run); + if (!tmpPoints) { + cgpoints.resize(glyphCount); + CTRunGetPositions(run, range, cgpoints.data()); + tmpPoints = cgpoints.constData(); + } + + const int rtlOffset = rtl ? (glyphCount - 1) : 0; + const int rtlSign = rtl ? -1 : 1; + + if (logClusters) { + CFRange stringRange = CTRunGetStringRange(run); + QVarLengthArray stringIndices(0); + const CFIndex *tmpIndices = CTRunGetStringIndicesPtr(run); + if (!tmpIndices) { + stringIndices.resize(glyphCount); + CTRunGetStringIndices(run, range, stringIndices.data()); + tmpIndices = stringIndices.constData(); + } + + const int firstGlyphIndex = outGlyphs - initialGlyph; + outAttributes[0].clusterStart = true; + + CFIndex k = 0; + CFIndex i = 0; + for (i = stringRange.location; + (i < stringRange.location + stringRange.length) && (k < glyphCount); ++i) { + if (tmpIndices[k * rtlSign + rtlOffset] == i || i == stringRange.location) { + logClusters[i] = k + firstGlyphIndex; + outAttributes[k].clusterStart = true; + ++k; + } else { + logClusters[i] = k + firstGlyphIndex - 1; + } + } + // in case of a ligature at the end, fill the remaining logcluster entries + for (;i < stringRange.location + stringRange.length; i++) { + logClusters[i] = k + firstGlyphIndex - 1; + } + } + for (CFIndex i = 0; i < glyphCount - 1; ++i) { + int idx = rtlOffset + rtlSign * i; + outGlyphs[idx] = tmpGlyphs[i] | fontIndex; + outAdvances_x[idx] = QFixed::fromReal(tmpPoints[i + 1].x - tmpPoints[i].x); + // Use negative y advance for flipped coordinate system + outAdvances_y[idx] = QFixed::fromReal(tmpPoints[i].y - tmpPoints[i + 1].y); + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + outAdvances_x[idx] = outAdvances_x[idx].round(); + outAdvances_y[idx] = outAdvances_y[idx].round(); + } + } + CGSize lastGlyphAdvance; + CTFontGetAdvancesForGlyphs(runFont, kCTFontHorizontalOrientation, tmpGlyphs + glyphCount - 1, &lastGlyphAdvance, 1); + + outGlyphs[rtl ? 0 : (glyphCount - 1)] = tmpGlyphs[glyphCount - 1] | fontIndex; + outAdvances_x[rtl ? 0 : (glyphCount - 1)] = + (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? QFixed::fromReal(lastGlyphAdvance.width).round() + : QFixed::fromReal(lastGlyphAdvance.width); + + if (endWithPDF) { + logClusters[stringRange.location + stringRange.length - 1] = glyphCount; + outGlyphs[glyphCount] = 0xFFFF; + outAdvances_x[glyphCount] = 0; + outAdvances_y[glyphCount] = 0; + outAttributes[glyphCount].clusterStart = true; + outAttributes[glyphCount].dontPrint = true; + glyphCount++; + } + } + outGlyphs += glyphCount; + outAttributes += glyphCount; + outAdvances_x += glyphCount; + outAdvances_y += glyphCount; + } + *nglyphs = (outGlyphs - initialGlyph); + return !outOBounds; +} + +bool QCoreTextFontEngineMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, + int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + *nglyphs = len; + QCFType cfstring; + + QVarLengthArray cgGlyphs(len); + CTFontGetGlyphsForCharacters(ctfont, (const UniChar*)str, cgGlyphs.data(), len); + + for (int i = 0; i < len; ++i) { + if (cgGlyphs[i]) { + glyphs->glyphs[i] = cgGlyphs[i]; + } else { + if (!cfstring) + cfstring = CFStringCreateWithCharactersNoCopy(0, reinterpret_cast(str), len, kCFAllocatorNull); + QCFType substituteFont = CTFontCreateForString(ctfont, cfstring, CFRangeMake(i, 1)); + CGGlyph substituteGlyph = 0; + CTFontGetGlyphsForCharacters(substituteFont, (const UniChar*)str + i, &substituteGlyph, 1); + if (substituteGlyph) { + const uint fontIndex = (fontIndexForFont(substituteFont) << 24); + glyphs->glyphs[i] = substituteGlyph | fontIndex; + if (!(flags & QTextEngine::GlyphIndicesOnly)) { + CGSize advance; + CTFontGetAdvancesForGlyphs(substituteFont, kCTFontHorizontalOrientation, &substituteGlyph, &advance, 1); + glyphs->advances_x[i] = QFixed::fromReal(advance.width); + glyphs->advances_y[i] = QFixed::fromReal(advance.height); + } + } + } + } + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + loadAdvancesForGlyphs(ctfont, cgGlyphs, glyphs, len, flags, fontDef); + return true; +} + +void QCoreTextFontEngineMulti::loadEngine(int) +{ + // Do nothing + Q_ASSERT(false); +} + +extern int qt_antialiasing_threshold; // from qapplication.cpp + +static inline CGAffineTransform transformFromFontDef(const QFontDef &fontDef) +{ + CGAffineTransform transform = CGAffineTransformIdentity; + if (fontDef.stretch != 100) + transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1); + return transform; +} + +QCoreTextFontEngine::QCoreTextFontEngine(CTFontRef font, const QFontDef &def) +{ + fontDef = def; + transform = transformFromFontDef(fontDef); + ctfont = font; + CFRetain(ctfont); + cgFont = CTFontCopyGraphicsFont(font, NULL); + init(); +} + +QCoreTextFontEngine::QCoreTextFontEngine(CGFontRef font, const QFontDef &def) +{ + fontDef = def; + transform = transformFromFontDef(fontDef); + cgFont = font; + // Keep reference count balanced + CFRetain(cgFont); + ctfont = CTFontCreateWithGraphicsFont(font, fontDef.pixelSize, &transform, NULL); + init(); +} + +QCoreTextFontEngine::~QCoreTextFontEngine() +{ + CFRelease(cgFont); + CFRelease(ctfont); +} + +extern QFont::Weight weightFromInteger(int weight); // qfontdatabase.cpp + +int getTraitValue(CFDictionaryRef allTraits, CFStringRef trait) +{ + if (CFDictionaryContainsKey(allTraits, trait)) { + CFNumberRef traitNum = (CFNumberRef) CFDictionaryGetValue(allTraits, trait); + float v = 0; + CFNumberGetValue(traitNum, kCFNumberFloatType, &v); + // the value we get from CFNumberRef is from -1.0 to 1.0 + int value = v * 500 + 500; + return value; + } + + return 0; +} + +void QCoreTextFontEngine::init() +{ + Q_ASSERT(ctfont != NULL); + Q_ASSERT(cgFont != NULL); + + QCFString family = CTFontCopyFamilyName(ctfont); + fontDef.family = family; + + synthesisFlags = 0; + CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ctfont); + if (traits & kCTFontItalicTrait) + fontDef.style = QFont::StyleItalic; + + CFDictionaryRef allTraits = CTFontCopyTraits(ctfont); + fontDef.weight = weightFromInteger(getTraitValue(allTraits, kCTFontWeightTrait)); + int slant = getTraitValue(allTraits, kCTFontSlantTrait); + if (slant > 500 && !(traits & kCTFontItalicTrait)) + fontDef.style = QFont::StyleOblique; + CFRelease(allTraits); + + if (fontDef.weight >= QFont::Bold && !(traits & kCTFontBoldTrait)) + synthesisFlags |= SynthesizedBold; + // XXX: we probably don't need to synthesis italic for oblique font + if (fontDef.style != QFont::StyleNormal && !(traits & kCTFontItalicTrait)) + synthesisFlags |= SynthesizedItalic; + + avgCharWidth = 0; + QByteArray os2Table = getSfntTable(MAKE_TAG('O', 'S', '/', '2')); + unsigned emSize = CTFontGetUnitsPerEm(ctfont); + if (os2Table.size() >= 10) { + fsType = qFromBigEndian(reinterpret_cast(os2Table.constData() + 8)); + // qAbs is a workaround for weird fonts like Lucida Grande + qint16 width = qAbs(qFromBigEndian(reinterpret_cast(os2Table.constData() + 2))); + avgCharWidth = QFixed::fromReal(width * fontDef.pixelSize / emSize); + } else + avgCharWidth = QFontEngine::averageCharWidth(); + + ctMaxCharWidth = ctMinLeftBearing = ctMinRightBearing = 0; + QByteArray hheaTable = getSfntTable(MAKE_TAG('h', 'h', 'e', 'a')); + if (hheaTable.size() >= 16) { + quint16 width = qFromBigEndian(reinterpret_cast(hheaTable.constData() + 10)); + ctMaxCharWidth = width * fontDef.pixelSize / emSize; + qint16 bearing = qFromBigEndian(reinterpret_cast(hheaTable.constData() + 12)); + ctMinLeftBearing = bearing * fontDef.pixelSize / emSize; + bearing = qFromBigEndian(reinterpret_cast(hheaTable.constData() + 14)); + ctMinRightBearing = bearing * fontDef.pixelSize / emSize; + } +} + +bool QCoreTextFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, + int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + *nglyphs = len; + QCFType cfstring; + + QVarLengthArray cgGlyphs(len); + CTFontGetGlyphsForCharacters(ctfont, (const UniChar*)str, cgGlyphs.data(), len); + + for (int i = 0; i < len; ++i) + if (cgGlyphs[i]) + glyphs->glyphs[i] = cgGlyphs[i]; + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + loadAdvancesForGlyphs(ctfont, cgGlyphs, glyphs, len, flags, fontDef); + return true; +} + +glyph_metrics_t QCoreTextFontEngine::boundingBox(const QGlyphLayout &glyphs) +{ + QFixed w; + bool round = fontDef.styleStrategy & QFont::ForceIntegerMetrics; + + for (int i = 0; i < glyphs.numGlyphs; ++i) { + w += round ? glyphs.effectiveAdvance(i).round() + : glyphs.effectiveAdvance(i); + } + return glyph_metrics_t(0, -(ascent()), w - lastRightBearing(glyphs, round), ascent()+descent(), w, 0); +} + +glyph_metrics_t QCoreTextFontEngine::boundingBox(glyph_t glyph) +{ + glyph_metrics_t ret; + CGGlyph g = glyph; + CGRect rect = CTFontGetBoundingRectsForGlyphs(ctfont, kCTFontHorizontalOrientation, &g, 0, 1); + if (synthesisFlags & QFontEngine::SynthesizedItalic) { + rect.size.width += rect.size.height * SYNTHETIC_ITALIC_SKEW; + } + ret.width = QFixed::fromReal(rect.size.width); + ret.height = QFixed::fromReal(rect.size.height); + ret.x = QFixed::fromReal(rect.origin.x); + ret.y = -QFixed::fromReal(rect.origin.y) - ret.height; + CGSize advances[1]; + CTFontGetAdvancesForGlyphs(ctfont, kCTFontHorizontalOrientation, &g, advances, 1); + ret.xoff = QFixed::fromReal(advances[0].width); + ret.yoff = QFixed::fromReal(advances[0].height); + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + ret.xoff = ret.xoff.round(); + ret.yoff = ret.yoff.round(); + } + + return ret; +} + +QFixed QCoreTextFontEngine::ascent() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? QFixed::fromReal(CTFontGetAscent(ctfont)).round() + : QFixed::fromReal(CTFontGetAscent(ctfont)); +} +QFixed QCoreTextFontEngine::descent() const +{ + QFixed d = QFixed::fromReal(CTFontGetDescent(ctfont)); + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + d = d.round(); + + // subtract a pixel to even out the historical +1 in QFontMetrics::height(). + // Fix in Qt 5. + return d - 1; +} +QFixed QCoreTextFontEngine::leading() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? QFixed::fromReal(CTFontGetLeading(ctfont)).round() + : QFixed::fromReal(CTFontGetLeading(ctfont)); +} +QFixed QCoreTextFontEngine::xHeight() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? QFixed::fromReal(CTFontGetXHeight(ctfont)).round() + : QFixed::fromReal(CTFontGetXHeight(ctfont)); +} + +QFixed QCoreTextFontEngine::averageCharWidth() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? avgCharWidth.round() : avgCharWidth; +} + +qreal QCoreTextFontEngine::maxCharWidth() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? qRound(ctMaxCharWidth) : ctMaxCharWidth; +} + +qreal QCoreTextFontEngine::minLeftBearing() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? qRound(ctMinLeftBearing) : ctMinLeftBearing; +} + +qreal QCoreTextFontEngine::minRightBearing() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? qRound(ctMinRightBearing) : ctMinLeftBearing; +} + +void QCoreTextFontEngine::draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight) +{ + QVarLengthArray positions; + QVarLengthArray glyphs; + QTransform matrix; + matrix.translate(x, y); + getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + CGContextSetFontSize(ctx, fontDef.pixelSize); + + CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); + + CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, -1, 0, -paintDeviceHeight); + + CGAffineTransformConcat(cgMatrix, oldTextMatrix); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0)); + + cgMatrix = CGAffineTransformConcat(cgMatrix, transform); + + CGContextSetTextMatrix(ctx, cgMatrix); + + CGContextSetTextDrawingMode(ctx, kCGTextFill); + + + QVarLengthArray advances(glyphs.size()); + QVarLengthArray cgGlyphs(glyphs.size()); + + for (int i = 0; i < glyphs.size() - 1; ++i) { + advances[i].width = (positions[i + 1].x - positions[i].x).toReal(); + advances[i].height = (positions[i + 1].y - positions[i].y).toReal(); + cgGlyphs[i] = glyphs[i]; + } + advances[glyphs.size() - 1].width = 0; + advances[glyphs.size() - 1].height = 0; + cgGlyphs[glyphs.size() - 1] = glyphs[glyphs.size() - 1]; + + CGContextSetFont(ctx, cgFont); + //NSLog(@"Font inDraw %@ ctfont %@", CGFontCopyFullName(cgFont), CTFontCopyFamilyName(ctfont)); + + CGContextSetTextPosition(ctx, positions[0].x.toReal(), positions[0].y.toReal()); + + CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); + + if (synthesisFlags & QFontEngine::SynthesizedBold) { + CGContextSetTextPosition(ctx, positions[0].x.toReal() + 0.5 * lineThickness().toReal(), + positions[0].y.toReal()); + + CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); + } + + CGContextSetTextMatrix(ctx, oldTextMatrix); +} + +struct ConvertPathInfo +{ + ConvertPathInfo(QPainterPath *newPath, const QPointF &newPos) : path(newPath), pos(newPos) {} + QPainterPath *path; + QPointF pos; +}; + +static void convertCGPathToQPainterPath(void *info, const CGPathElement *element) +{ + ConvertPathInfo *myInfo = static_cast(info); + switch(element->type) { + case kCGPathElementMoveToPoint: + myInfo->path->moveTo(element->points[0].x + myInfo->pos.x(), + element->points[0].y + myInfo->pos.y()); + break; + case kCGPathElementAddLineToPoint: + myInfo->path->lineTo(element->points[0].x + myInfo->pos.x(), + element->points[0].y + myInfo->pos.y()); + break; + case kCGPathElementAddQuadCurveToPoint: + myInfo->path->quadTo(element->points[0].x + myInfo->pos.x(), + element->points[0].y + myInfo->pos.y(), + element->points[1].x + myInfo->pos.x(), + element->points[1].y + myInfo->pos.y()); + break; + case kCGPathElementAddCurveToPoint: + myInfo->path->cubicTo(element->points[0].x + myInfo->pos.x(), + element->points[0].y + myInfo->pos.y(), + element->points[1].x + myInfo->pos.x(), + element->points[1].y + myInfo->pos.y(), + element->points[2].x + myInfo->pos.x(), + element->points[2].y + myInfo->pos.y()); + break; + case kCGPathElementCloseSubpath: + myInfo->path->closeSubpath(); + break; + default: + qDebug() << "Unhandled path transform type: " << element->type; + } + +} + +void QCoreTextFontEngine::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nGlyphs, + QPainterPath *path, QTextItem::RenderFlags) +{ + CGAffineTransform cgMatrix = CGAffineTransformIdentity; + cgMatrix = CGAffineTransformScale(cgMatrix, 1, -1); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0)); + + for (int i = 0; i < nGlyphs; ++i) { + QCFType cgpath = CTFontCreatePathForGlyph(ctfont, glyphs[i], &cgMatrix); + ConvertPathInfo info(path, positions[i].toPointF()); + CGPathApply(cgpath, &info, convertCGPathToQPainterPath); + } +} + +QImage QCoreTextFontEngine::imageForGlyph(glyph_t glyph, QFixed subPixelPosition, int /*margin*/, bool aa) +{ + const glyph_metrics_t br = boundingBox(glyph); + QImage im(qRound(br.width)+2, qRound(br.height)+2, QImage::Format_RGB32); + im.fill(0); + + CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + uint cgflags = kCGImageAlphaNoneSkipFirst; +#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version + cgflags |= kCGBitmapByteOrder32Host; +#endif + CGContextRef ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(), + 8, im.bytesPerLine(), colorspace, + cgflags); + CGContextSetFontSize(ctx, fontDef.pixelSize); + CGContextSetShouldAntialias(ctx, aa || + (fontDef.pointSize > qt_antialiasing_threshold + && !(fontDef.styleStrategy & QFont::NoAntialias))); + CGContextSetShouldSmoothFonts(ctx, aa); + CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); + CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, 1, 0, 0); + + CGAffineTransformConcat(cgMatrix, oldTextMatrix); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, SYNTHETIC_ITALIC_SKEW, 1, 0, 0)); + + cgMatrix = CGAffineTransformConcat(cgMatrix, transform); + + CGContextSetTextMatrix(ctx, cgMatrix); + CGContextSetRGBFillColor(ctx, 1, 1, 1, 1); + CGContextSetTextDrawingMode(ctx, kCGTextFill); + + CGContextSetFont(ctx, cgFont); + + qreal pos_x = -br.x.toReal() + subPixelPosition.toReal(); + qreal pos_y = im.height() + br.y.toReal() - 1; + CGContextSetTextPosition(ctx, pos_x, pos_y); + + CGSize advance; + advance.width = 0; + advance.height = 0; + CGGlyph cgGlyph = glyph; + CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); + + if (synthesisFlags & QFontEngine::SynthesizedBold) { + CGContextSetTextPosition(ctx, pos_x + 0.5 * lineThickness().toReal(), pos_y); + CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); + } + + CGContextRelease(ctx); + + return im; +} + +QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition) +{ + QImage im = imageForGlyph(glyph, subPixelPosition, 0, false); + + QImage indexed(im.width(), im.height(), QImage::Format_Indexed8); + QVector colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; y= QTransform::TxScale) + return QFontEngine::alphaRGBMapForGlyph(glyph, subPixelPosition, margin, x); + + QImage im = imageForGlyph(glyph, subPixelPosition, margin, true); + qGamma_correct_back_to_linear_cs(&im); + return im; +} + +void QCoreTextFontEngine::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + int i, numGlyphs = glyphs->numGlyphs; + QVarLengthArray cgGlyphs(numGlyphs); + + for (i = 0; i < numGlyphs; ++i) { + if (glyphs->glyphs[i] & 0xff000000) + cgGlyphs[i] = 0; + else + cgGlyphs[i] = glyphs->glyphs[i]; + } + + loadAdvancesForGlyphs(ctfont, cgGlyphs, glyphs, numGlyphs, flags, fontDef); +} + +QFontEngine::FaceId QCoreTextFontEngine::faceId() const +{ + return QFontEngine::FaceId(); +} + +bool QCoreTextFontEngine::canRender(const QChar *string, int len) +{ + QVarLengthArray cgGlyphs(len); + return CTFontGetGlyphsForCharacters(ctfont, (const UniChar *) string, cgGlyphs.data(), len); +} + +bool QCoreTextFontEngine::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + QCFType table = CTFontCopyTable(ctfont, tag, 0); + if (!table || !length) + return false; + CFIndex tableLength = CFDataGetLength(table); + int availableLength = *length; + *length = tableLength; + if (buffer) { + if (tableLength > availableLength) + return false; + CFDataGetBytes(table, CFRangeMake(0, tableLength), buffer); + } + return true; +} + +void QCoreTextFontEngine::getUnscaledGlyph(glyph_t, QPainterPath *, glyph_metrics_t *) +{ + // ### +} + +QFixed QCoreTextFontEngine::emSquareSize() const +{ + return QFixed::QFixed(int(CTFontGetUnitsPerEm(ctfont))); +} + +QT_END_NAMESPACE + +#endif// !defined(Q_WS_MAC) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + diff --git a/src/gui/text/qfontengine_coretext_p.h b/src/gui/text/qfontengine_coretext_p.h new file mode 100644 index 0000000000..1503c3f73f --- /dev/null +++ b/src/gui/text/qfontengine_coretext_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_CORETEXT_P_H +#define QFONTENGINE_CORETEXT_P_H + +#include + +#if !defined(Q_WS_MAC) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + +class QRawFontPrivate; +class QCoreTextFontEngineMulti; +class QCoreTextFontEngine : public QFontEngine +{ +public: + QCoreTextFontEngine(CTFontRef font, const QFontDef &def); + QCoreTextFontEngine(CGFontRef font, const QFontDef &def); + ~QCoreTextFontEngine(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual qreal maxCharWidth() const; + virtual QFixed averageCharWidth() const; + + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, + QPainterPath *path, QTextItem::RenderFlags); + + virtual const char *name() const { return "QCoreTextFontEngine"; } + + virtual bool canRender(const QChar *string, int len); + + virtual int synthesized() const { return synthesisFlags; } + virtual bool supportsSubPixelPositions() const { return true; } + + virtual Type type() const { return QFontEngine::Mac; } + + void draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight); + + virtual FaceId faceId() const; + virtual bool getSfntTableData(uint /*tag*/, uchar * /*buffer*/, uint * /*length*/) const; + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + virtual QImage alphaMapForGlyph(glyph_t, QFixed subPixelPosition); + virtual QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, int margin, const QTransform &t); + virtual qreal minRightBearing() const; + virtual qreal minLeftBearing() const; + virtual QFixed emSquareSize() const; + +private: + friend class QRawFontPrivate; + + void init(); + QImage imageForGlyph(glyph_t glyph, QFixed subPixelPosition, int margin, bool colorful); + CTFontRef ctfont; + CGFontRef cgFont; + int synthesisFlags; + CGAffineTransform transform; + QFixed avgCharWidth; + qreal ctMaxCharWidth; + qreal ctMinLeftBearing; + qreal ctMinRightBearing; + friend class QCoreTextFontEngineMulti; +}; + +class QCoreTextFontEngineMulti : public QFontEngineMulti +{ +public: + QCoreTextFontEngineMulti(const QCFString &name, const QFontDef &fontDef, bool kerning); + QCoreTextFontEngineMulti(CGFontRef cgFontRef, const QFontDef &fontDef, bool kerning); + ~QCoreTextFontEngineMulti(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags) const; + bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags, + unsigned short *logClusters, const HB_CharAttributes *charAttributes) const; + + virtual const char *name() const { return "CoreText"; } +protected: + virtual void loadEngine(int at); + +private: + void init(bool kerning); + inline const QCoreTextFontEngine *engineAt(int i) const + { return static_cast(engines.at(i)); } + + uint fontIndexForFont(CTFontRef font) const; + CTFontRef ctfont; + mutable QCFType attributeDict; + CGAffineTransform transform; + friend class QFontDialogPrivate; +}; + +#endif// !defined(Q_WS_MAC) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + +#endif // QFONTENGINE_CORETEXT_P_H diff --git a/src/gui/text/qfontengine_ft.cpp b/src/gui/text/qfontengine_ft.cpp new file mode 100644 index 0000000000..8f2da9b713 --- /dev/null +++ b/src/gui/text/qfontengine_ft.cpp @@ -0,0 +1,2074 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdir.h" +#include "qmetatype.h" +#include "qtextstream.h" +#include "qvariant.h" +#include "qfontengine_ft_p.h" + +#ifndef QT_NO_FREETYPE + +#include "qfile.h" +#include "qabstractfileengine.h" +#include "qthreadstorage.h" +#include +#include + +#include "qfontengine_ft_p.h" +#include +#include FT_FREETYPE_H +#include FT_OUTLINE_H +#include FT_SYNTHESIS_H +#include FT_TRUETYPE_TABLES_H +#include FT_TYPE1_TABLES_H +#include FT_GLYPH_H + +#if defined(FT_LCD_FILTER_H) +#include FT_LCD_FILTER_H +#endif + +#if defined(FT_CONFIG_OPTIONS_H) +#include FT_CONFIG_OPTIONS_H +#endif + +#if defined(FT_LCD_FILTER_H) && defined(FT_CONFIG_OPTION_SUBPIXEL_RENDERING) +#define QT_USE_FREETYPE_LCDFILTER +#endif + +#ifdef QT_LINUXBASE +#include FT_ERRORS_H +#endif + +QT_BEGIN_NAMESPACE + +/* + * Freetype 2.1.7 and earlier used width/height + * for matching sizes in the BDF and PCF loaders. + * This has been fixed for 2.1.8. + */ +#if (FREETYPE_MAJOR*10000+FREETYPE_MINOR*100+FREETYPE_PATCH) >= 20105 +#define X_SIZE(face,i) ((face)->available_sizes[i].x_ppem) +#define Y_SIZE(face,i) ((face)->available_sizes[i].y_ppem) +#else +#define X_SIZE(face,i) ((face)->available_sizes[i].width << 6) +#define Y_SIZE(face,i) ((face)->available_sizes[i].height << 6) +#endif + +/* FreeType 2.1.10 starts to provide FT_GlyphSlot_Embolden */ +#if (FREETYPE_MAJOR*10000+FREETYPE_MINOR*100+FREETYPE_PATCH) >= 20110 +#define Q_FT_GLYPHSLOT_EMBOLDEN(slot) FT_GlyphSlot_Embolden(slot) +#else +#define Q_FT_GLYPHSLOT_EMBOLDEN(slot) +#endif + +#define FLOOR(x) ((x) & -64) +#define CEIL(x) (((x)+63) & -64) +#define TRUNC(x) ((x) >> 6) +#define ROUND(x) (((x)+32) & -64) + +static HB_Error hb_getSFntTable(void *font, HB_Tag tableTag, HB_Byte *buffer, HB_UInt *length) +{ +#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) > 20103 + FT_Face face = (FT_Face)font; + FT_ULong ftlen = *length; + FT_Error error = 0; + + if ( !FT_IS_SFNT(face) ) + return HB_Err_Invalid_Argument; + + error = FT_Load_Sfnt_Table(face, tableTag, 0, buffer, &ftlen); + *length = ftlen; + return (HB_Error)error; +#else + return HB_Err_Invalid_Argument; +#endif +} + +// -------------------------- Freetype support ------------------------------ + +class QtFreetypeData +{ +public: + QtFreetypeData() + : library(0) + { } + + FT_Library library; + QHash faces; +}; + +#ifdef QT_NO_THREAD +Q_GLOBAL_STATIC(QtFreetypeData, theFreetypeData) + +QtFreetypeData *qt_getFreetypeData() +{ + return theFreetypeData(); +} +#else +Q_GLOBAL_STATIC(QThreadStorage, theFreetypeData) + +QtFreetypeData *qt_getFreetypeData() +{ + QtFreetypeData *&freetypeData = theFreetypeData()->localData(); + if (!freetypeData) + freetypeData = new QtFreetypeData; + return freetypeData; +} +#endif + +FT_Library qt_getFreetype() +{ + QtFreetypeData *freetypeData = qt_getFreetypeData(); + if (!freetypeData->library) + FT_Init_FreeType(&freetypeData->library); + return freetypeData->library; +} + +int QFreetypeFace::fsType() const +{ + int fsType = 0; + TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(face, ft_sfnt_os2); + if (os2) + fsType = os2->fsType; + return fsType; +} + +HB_Error QFreetypeFace::getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints) +{ + if (HB_Error error = (HB_Error)FT_Load_Glyph(face, glyph, flags)) + return error; + + if (face->glyph->format != FT_GLYPH_FORMAT_OUTLINE) + return HB_Err_Invalid_SubTable; + + *nPoints = face->glyph->outline.n_points; + if (!(*nPoints)) + return HB_Err_Ok; + + if (point > *nPoints) + return HB_Err_Invalid_SubTable; + + *xpos = face->glyph->outline.points[point].x; + *ypos = face->glyph->outline.points[point].y; + + return HB_Err_Ok; +} + +/* + * One font file can contain more than one font (bold/italic for example) + * find the right one and return it. + * + * Returns the freetype face or 0 in case of an empty file or any other problems + * (like not being able to open the file) + */ +QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id, + const QByteArray &fontData) +{ + if (face_id.filename.isEmpty() && fontData.isEmpty()) + return 0; + + QtFreetypeData *freetypeData = qt_getFreetypeData(); + if (!freetypeData->library) + FT_Init_FreeType(&freetypeData->library); + + QFreetypeFace *freetype = freetypeData->faces.value(face_id, 0); + if (freetype) { + freetype->ref.ref(); + } else { + QScopedPointer newFreetype(new QFreetypeFace); + FT_Face face; + if (!face_id.filename.isEmpty()) { + QFile file(QString::fromUtf8(face_id.filename)); + if (face_id.filename.startsWith(":qmemoryfonts/")) { + // from qfontdatabase.cpp + extern QByteArray qt_fontdata_from_index(int); + QByteArray idx = face_id.filename; + idx.remove(0, 14); // remove ':qmemoryfonts/' + bool ok = false; + newFreetype->fontData = qt_fontdata_from_index(idx.toInt(&ok)); + if (!ok) + newFreetype->fontData = QByteArray(); + } else if (!(file.fileEngine()->fileFlags(QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::LocalDiskFlag)) { + if (!file.open(QIODevice::ReadOnly)) { + return 0; + } + newFreetype->fontData = file.readAll(); + } + } else { + newFreetype->fontData = fontData; + } + if (!newFreetype->fontData.isEmpty()) { + if (FT_New_Memory_Face(freetypeData->library, (const FT_Byte *)newFreetype->fontData.constData(), newFreetype->fontData.size(), face_id.index, &face)) { + return 0; + } + } else if (FT_New_Face(freetypeData->library, face_id.filename, face_id.index, &face)) { + return 0; + } + newFreetype->face = face; + + newFreetype->hbFace = qHBNewFace(face, hb_getSFntTable); + Q_CHECK_PTR(newFreetype->hbFace); + newFreetype->ref = 1; + newFreetype->xsize = 0; + newFreetype->ysize = 0; + newFreetype->matrix.xx = 0x10000; + newFreetype->matrix.yy = 0x10000; + newFreetype->matrix.xy = 0; + newFreetype->matrix.yx = 0; + newFreetype->unicode_map = 0; + newFreetype->symbol_map = 0; +#ifndef QT_NO_FONTCONFIG + newFreetype->charset = 0; +#endif + + memset(newFreetype->cmapCache, 0, sizeof(newFreetype->cmapCache)); + + for (int i = 0; i < newFreetype->face->num_charmaps; ++i) { + FT_CharMap cm = newFreetype->face->charmaps[i]; + switch(cm->encoding) { + case FT_ENCODING_UNICODE: + newFreetype->unicode_map = cm; + break; + case FT_ENCODING_APPLE_ROMAN: + case FT_ENCODING_ADOBE_LATIN_1: + if (!newFreetype->unicode_map || newFreetype->unicode_map->encoding != FT_ENCODING_UNICODE) + newFreetype->unicode_map = cm; + break; + case FT_ENCODING_ADOBE_CUSTOM: + case FT_ENCODING_MS_SYMBOL: + if (!newFreetype->symbol_map) + newFreetype->symbol_map = cm; + break; + default: + break; + } + } + + if (!FT_IS_SCALABLE(newFreetype->face) && newFreetype->face->num_fixed_sizes == 1) + FT_Set_Char_Size (face, X_SIZE(newFreetype->face, 0), Y_SIZE(newFreetype->face, 0), 0, 0); +# if 0 + FcChar8 *name; + FcPatternGetString(pattern, FC_FAMILY, 0, &name); + qDebug("%s: using maps: default: %x unicode: %x, symbol: %x", name, + newFreetype->face->charmap ? newFreetype->face->charmap->encoding : 0, + newFreetype->unicode_map ? newFreetype->unicode_map->encoding : 0, + newFreetype->symbol_map ? newFreetype->symbol_map->encoding : 0); + + for (int i = 0; i < 256; i += 8) + qDebug(" %x: %d %d %d %d %d %d %d %d", i, + FcCharSetHasChar(newFreetype->charset, i), FcCharSetHasChar(newFreetype->charset, i), + FcCharSetHasChar(newFreetype->charset, i), FcCharSetHasChar(newFreetype->charset, i), + FcCharSetHasChar(newFreetype->charset, i), FcCharSetHasChar(newFreetype->charset, i), + FcCharSetHasChar(newFreetype->charset, i), FcCharSetHasChar(newFreetype->charset, i)); +#endif + + FT_Set_Charmap(newFreetype->face, newFreetype->unicode_map); + QT_TRY { + freetypeData->faces.insert(face_id, newFreetype.data()); + } QT_CATCH(...) { + newFreetype.take()->release(face_id); + // we could return null in principle instead of throwing + QT_RETHROW; + } + freetype = newFreetype.take(); + } + return freetype; +} + +void QFreetypeFace::release(const QFontEngine::FaceId &face_id) +{ + QtFreetypeData *freetypeData = qt_getFreetypeData(); + if (!ref.deref()) { + qHBFreeFace(hbFace); + FT_Done_Face(face); +#ifndef QT_NO_FONTCONFIG + if (charset) + FcCharSetDestroy(charset); +#endif + if(freetypeData->faces.contains(face_id)) + freetypeData->faces.take(face_id); + delete this; + } + if (freetypeData->faces.isEmpty()) { + FT_Done_FreeType(freetypeData->library); + freetypeData->library = 0; + } +} + + +void QFreetypeFace::computeSize(const QFontDef &fontDef, int *xsize, int *ysize, bool *outline_drawing) +{ + *ysize = qRound(fontDef.pixelSize * 64); + *xsize = *ysize * fontDef.stretch / 100; + *outline_drawing = false; + + /* + * Bitmap only faces must match exactly, so find the closest + * one (height dominant search) + */ + if (!(face->face_flags & FT_FACE_FLAG_SCALABLE)) { + int best = 0; + for (int i = 1; i < face->num_fixed_sizes; i++) { + if (qAbs(*ysize - Y_SIZE(face,i)) < + qAbs (*ysize - Y_SIZE(face, best)) || + (qAbs (*ysize - Y_SIZE(face, i)) == + qAbs (*ysize - Y_SIZE(face, best)) && + qAbs (*xsize - X_SIZE(face, i)) < + qAbs (*xsize - X_SIZE(face, best)))) { + best = i; + } + } + if (FT_Set_Char_Size (face, X_SIZE(face, best), Y_SIZE(face, best), 0, 0) == 0) { + *xsize = X_SIZE(face, best); + *ysize = Y_SIZE(face, best); + } else { + int err = 1; + if (!(face->face_flags & FT_FACE_FLAG_SCALABLE) && ysize == 0 && face->num_fixed_sizes >= 1) { + // work around FT 2.1.10 problem with BDF without PIXEL_SIZE property + err = FT_Set_Pixel_Sizes(face, face->available_sizes[0].width, face->available_sizes[0].height); + if (err && face->num_fixed_sizes == 1) + err = 0; //even more of a workaround... + } + + if (err) + *xsize = *ysize = 0; + } + } else { + *outline_drawing = (*xsize > (64<<6) || *ysize > (64<<6)); + } +} + +QFontEngine::Properties QFreetypeFace::properties() const +{ + QFontEngine::Properties p; + p.postscriptName = FT_Get_Postscript_Name(face); + PS_FontInfoRec font_info; + if (FT_Get_PS_Font_Info(face, &font_info) == 0) + p.copyright = font_info.notice; + if (FT_IS_SCALABLE(face)) { + p.ascent = face->ascender; + p.descent = -face->descender; + p.leading = face->height - face->ascender + face->descender; + p.emSquare = face->units_per_EM; + p.boundingBox = QRectF(face->bbox.xMin, -face->bbox.yMax, + face->bbox.xMax - face->bbox.xMin, + face->bbox.yMax - face->bbox.yMin); + } else { + p.ascent = QFixed::fromFixed(face->size->metrics.ascender); + p.descent = QFixed::fromFixed(-face->size->metrics.descender); + p.leading = QFixed::fromFixed(face->size->metrics.height - face->size->metrics.ascender + face->size->metrics.descender); + p.emSquare = face->size->metrics.y_ppem; +// p.boundingBox = QRectF(-p.ascent.toReal(), 0, (p.ascent + p.descent).toReal(), face->size->metrics.max_advance/64.); + p.boundingBox = QRectF(0, -p.ascent.toReal(), + face->size->metrics.max_advance/64, (p.ascent + p.descent).toReal() ); + } + p.italicAngle = 0; + p.capHeight = p.ascent; + p.lineWidth = face->underline_thickness; + return p; +} + +bool QFreetypeFace::getSfntTable(uint tag, uchar *buffer, uint *length) const +{ + bool result = false; +#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) > 20103 + if (FT_IS_SFNT(face)) { + FT_ULong len = *length; + result = FT_Load_Sfnt_Table(face, tag, 0, buffer, &len) == FT_Err_Ok; + *length = len; + } +#endif + return result; +} + +/* Some fonts (such as MingLiu rely on hinting to scale different + components to their correct sizes. While this is really broken (it + should be done in the component glyph itself, not the hinter) we + will have to live with it. + + This means we can not use FT_LOAD_NO_HINTING to get the glyph + outline. All we can do is to load the unscaled glyph and scale it + down manually when required. +*/ +static void scaleOutline(FT_Face face, FT_GlyphSlot g, FT_Fixed x_scale, FT_Fixed y_scale) +{ + x_scale = FT_MulDiv(x_scale, 1 << 10, face->units_per_EM); + y_scale = FT_MulDiv(y_scale, 1 << 10, face->units_per_EM); + FT_Vector *p = g->outline.points; + const FT_Vector *e = p + g->outline.n_points; + while (p < e) { + p->x = FT_MulFix(p->x, x_scale); + p->y = FT_MulFix(p->y, y_scale); + ++p; + } +} + +void QFreetypeFace::addGlyphToPath(FT_Face face, FT_GlyphSlot g, const QFixedPoint &point, QPainterPath *path, FT_Fixed x_scale, FT_Fixed y_scale) +{ + const qreal factor = 1/64.; + scaleOutline(face, g, x_scale, y_scale); + + QPointF cp = point.toPointF(); + + // convert the outline to a painter path + int i = 0; + for (int j = 0; j < g->outline.n_contours; ++j) { + int last_point = g->outline.contours[j]; + QPointF start = cp + QPointF(g->outline.points[i].x*factor, -g->outline.points[i].y*factor); + if(!(g->outline.tags[i] & 1)) { + start += cp + QPointF(g->outline.points[last_point].x*factor, -g->outline.points[last_point].y*factor); + start /= 2; + } +// qDebug("contour: %d -- %d", i, g->outline.contours[j]); +// qDebug("first point at %f %f", start.x(), start.y()); + path->moveTo(start); + + QPointF c[4]; + c[0] = start; + int n = 1; + while (i < last_point) { + ++i; + c[n] = cp + QPointF(g->outline.points[i].x*factor, -g->outline.points[i].y*factor); +// qDebug() << " i=" << i << " flag=" << (int)g->outline.tags[i] << "point=" << c[n]; + ++n; + switch (g->outline.tags[i] & 3) { + case 2: + // cubic bezier element + if (n < 4) + continue; + c[3] = (c[3] + c[2])/2; + --i; + break; + case 0: + // quadratic bezier element + if (n < 3) + continue; + c[3] = (c[1] + c[2])/2; + c[2] = (2*c[1] + c[3])/3; + c[1] = (2*c[1] + c[0])/3; + --i; + break; + case 1: + case 3: + if (n == 2) { +// qDebug() << "lineTo" << c[1]; + path->lineTo(c[1]); + c[0] = c[1]; + n = 1; + continue; + } else if (n == 3) { + c[3] = c[2]; + c[2] = (2*c[1] + c[3])/3; + c[1] = (2*c[1] + c[0])/3; + } + break; + } +// qDebug() << "cubicTo" << c[1] << c[2] << c[3]; + path->cubicTo(c[1], c[2], c[3]); + c[0] = c[3]; + n = 1; + } + if (n == 1) { +// qDebug() << "closeSubpath"; + path->closeSubpath(); + } else { + c[3] = start; + if (n == 2) { + c[2] = (2*c[1] + c[3])/3; + c[1] = (2*c[1] + c[0])/3; + } +// qDebug() << "cubicTo" << c[1] << c[2] << c[3]; + path->cubicTo(c[1], c[2], c[3]); + } + ++i; + } +} + +extern void qt_addBitmapToPath(qreal x0, qreal y0, const uchar *image_data, int bpl, int w, int h, QPainterPath *path); + +void QFreetypeFace::addBitmapToPath(FT_GlyphSlot slot, const QFixedPoint &point, QPainterPath *path, bool) +{ + if (slot->format != FT_GLYPH_FORMAT_BITMAP + || slot->bitmap.pixel_mode != FT_PIXEL_MODE_MONO) + return; + + QPointF cp = point.toPointF(); + qt_addBitmapToPath(cp.x() + TRUNC(slot->metrics.horiBearingX), cp.y() - TRUNC(slot->metrics.horiBearingY), + slot->bitmap.buffer, slot->bitmap.pitch, slot->bitmap.width, slot->bitmap.rows, path); +} + +QFontEngineFT::Glyph::~Glyph() +{ + delete [] data; +} + +static const uint subpixel_filter[3][3] = { + { 180, 60, 16 }, + { 38, 180, 38 }, + { 16, 60, 180 } +}; + +static inline uint filterPixel(uint red, uint green, uint blue, bool legacyFilter) +{ + uint res; + if (legacyFilter) { + uint high = (red*subpixel_filter[0][0] + green*subpixel_filter[0][1] + blue*subpixel_filter[0][2]) >> 8; + uint mid = (red*subpixel_filter[1][0] + green*subpixel_filter[1][1] + blue*subpixel_filter[1][2]) >> 8; + uint low = (red*subpixel_filter[2][0] + green*subpixel_filter[2][1] + blue*subpixel_filter[2][2]) >> 8; + res = (mid << 24) + (high << 16) + (mid << 8) + low; + } else { + uint alpha = green; + res = (alpha << 24) + (red << 16) + (green << 8) + blue; + } + return res; +} + +static void convertRGBToARGB(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr, bool legacyFilter) +{ + int h = height; + const int offs = bgr ? -1 : 1; + const int w = width * 3; + while (h--) { + uint *dd = dst; + for (int x = 0; x < w; x += 3) { + uint red = src[x+1-offs]; + uint green = src[x+1]; + uint blue = src[x+1+offs]; + *dd = filterPixel(red, green, blue, legacyFilter); + ++dd; + } + dst += width; + src += src_pitch; + } +} + +static void convertRGBToARGB_V(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr, bool legacyFilter) +{ + int h = height; + const int offs = bgr ? -src_pitch : src_pitch; + while (h--) { + for (int x = 0; x < width; x++) { + uint red = src[x+src_pitch-offs]; + uint green = src[x+src_pitch]; + uint blue = src[x+src_pitch+offs]; + dst[x] = filterPixel(red, green, blue, legacyFilter); + } + dst += width; + src += 3*src_pitch; + } +} + +static void convoluteBitmap(const uchar *src, uchar *dst, int width, int height, int pitch) +{ + // convolute the bitmap with a triangle filter to get rid of color fringes + // If we take account for a gamma value of 2, we end up with + // weights of 1, 4, 9, 4, 1. We use an approximation of 1, 3, 8, 3, 1 here, + // as this nicely sums up to 16 :) + int h = height; + while (h--) { + dst[0] = dst[1] = 0; + // + for (int x = 2; x < width - 2; ++x) { + uint sum = src[x-2] + 3*src[x-1] + 8*src[x] + 3*src[x+1] + src[x+2]; + dst[x] = (uchar) (sum >> 4); + } + dst[width - 2] = dst[width - 1] = 0; + src += pitch; + dst += pitch; + } +} + +QFontEngineFT::QFontEngineFT(const QFontDef &fd) +{ + fontDef = fd; + matrix.xx = 0x10000; + matrix.yy = 0x10000; + matrix.xy = 0; + matrix.yx = 0; + cache_cost = 100; + kerning_pairs_loaded = false; + transform = false; + embolden = false; + antialias = true; + freetype = 0; + default_load_flags = FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; + default_hint_style = HintNone; + subpixelType = Subpixel_None; + lcdFilterType = 0; +#if defined(FT_LCD_FILTER_H) + lcdFilterType = (int)((quintptr) FT_LCD_FILTER_DEFAULT); +#endif + defaultFormat = Format_None; + canUploadGlyphsToServer = false; + embeddedbitmap = false; +} + +QFontEngineFT::~QFontEngineFT() +{ + if (freetype) + freetype->release(face_id); + hbFace = 0; // we share the face in QFreeTypeFace, don't let ~QFontEngine delete it +} + +void QFontEngineFT::freeGlyphSets() +{ + freeServerGlyphSet(defaultGlyphSet.id); + for (int i = 0; i < transformedGlyphSets.count(); ++i) + freeServerGlyphSet(transformedGlyphSets.at(i).id); +} + +bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, + const QByteArray &fontData) +{ + return init(faceId, antialias, format, QFreetypeFace::getFace(faceId, fontData)); +} + +bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, + QFreetypeFace *freetypeFace) +{ + freetype = freetypeFace; + if (!freetype) { + xsize = 0; + ysize = 0; + return false; + } + defaultFormat = format; + this->antialias = antialias; + + if (!antialias) + glyphFormat = QFontEngineGlyphCache::Raster_Mono; + else if (format == Format_A8) + glyphFormat = QFontEngineGlyphCache::Raster_A8; + else if (format == Format_A32) + glyphFormat = QFontEngineGlyphCache::Raster_RGBMask; + + face_id = faceId; + + symbol = freetype->symbol_map != 0; + PS_FontInfoRec psrec; + // don't assume that type1 fonts are symbol fonts by default + if (FT_Get_PS_Font_Info(freetype->face, &psrec) == FT_Err_Ok) { + symbol = bool(fontDef.family.contains(QLatin1String("symbol"), Qt::CaseInsensitive)); + } + // ##### + freetype->hbFace->isSymbolFont = symbol; + + lbearing = rbearing = SHRT_MIN; + freetype->computeSize(fontDef, &xsize, &ysize, &defaultGlyphSet.outline_drawing); + + FT_Face face = lockFace(); + + if (FT_IS_SCALABLE(face)) { + bool fake_oblique = (fontDef.style != QFont::StyleNormal) && !(face->style_flags & FT_STYLE_FLAG_ITALIC); + if (fake_oblique) + matrix.xy = 0x10000*3/10; + FT_Set_Transform(face, &matrix, 0); + freetype->matrix = matrix; + if (fake_oblique) + transform = true; + // fake bold + if ((fontDef.weight == QFont::Bold) && !(face->style_flags & FT_STYLE_FLAG_BOLD) && !FT_IS_FIXED_WIDTH(face)) + embolden = true; + // underline metrics + line_thickness = QFixed::fromFixed(FT_MulFix(face->underline_thickness, face->size->metrics.y_scale)); + underline_position = QFixed::fromFixed(-FT_MulFix(face->underline_position, face->size->metrics.y_scale)); + } else { + // copied from QFontEngineQPF + // ad hoc algorithm + int score = fontDef.weight * fontDef.pixelSize; + line_thickness = score / 700; + // looks better with thicker line for small pointsizes + if (line_thickness < 2 && score >= 1050) + line_thickness = 2; + underline_position = ((line_thickness * 2) + 3) / 6; + } + if (line_thickness < 1) + line_thickness = 1; + + hbFont.x_ppem = face->size->metrics.x_ppem; + hbFont.y_ppem = face->size->metrics.y_ppem; + hbFont.x_scale = face->size->metrics.x_scale; + hbFont.y_scale = face->size->metrics.y_scale; + + hbFace = freetype->hbFace; + + metrics = face->size->metrics; + +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) + /* + TrueType fonts with embedded bitmaps may have a bitmap font specific + ascent/descent in the EBLC table. There is no direct public API + to extract those values. The only way we've found is to trick freetype + into thinking that it's not a scalable font in FT_SelectSize so that + the metrics are retrieved from the bitmap strikes. + */ + if (FT_IS_SCALABLE(face)) { + for (int i = 0; i < face->num_fixed_sizes; ++i) { + if (xsize == X_SIZE(face, i) && ysize == Y_SIZE(face, i)) { + face->face_flags &= ~FT_FACE_FLAG_SCALABLE; + + FT_Select_Size(face, i); + metrics.ascender = face->size->metrics.ascender; + metrics.descender = face->size->metrics.descender; + FT_Set_Char_Size(face, xsize, ysize, 0, 0); + + face->face_flags |= FT_FACE_FLAG_SCALABLE; + break; + } + } + } +#endif + + unlockFace(); + + fsType = freetype->fsType(); + defaultGlyphSet.id = allocateServerGlyphSet(); + return true; +} + +void QFontEngineFT::setDefaultHintStyle(HintStyle style) +{ + default_hint_style = style; +} + +int QFontEngineFT::loadFlags(QGlyphSet *set, GlyphFormat format, int flags, + bool &hsubpixel, int &vfactor) const +{ + int load_flags = FT_LOAD_DEFAULT | default_load_flags; + int load_target = default_hint_style == HintLight + ? FT_LOAD_TARGET_LIGHT + : FT_LOAD_TARGET_NORMAL; + + if (format == Format_Mono) { + load_target = FT_LOAD_TARGET_MONO; + } else if (format == Format_A32) { + if (subpixelType == QFontEngineFT::Subpixel_RGB || subpixelType == QFontEngineFT::Subpixel_BGR) { + if (default_hint_style == HintFull) + load_target = FT_LOAD_TARGET_LCD; + hsubpixel = true; + } else if (subpixelType == QFontEngineFT::Subpixel_VRGB || subpixelType == QFontEngineFT::Subpixel_VBGR) { + if (default_hint_style == HintFull) + load_target = FT_LOAD_TARGET_LCD_V; + vfactor = 3; + } + } + + if (set && set->outline_drawing) + load_flags = FT_LOAD_NO_BITMAP; + + if (default_hint_style == HintNone || (flags & HB_ShaperFlag_UseDesignMetrics)) + load_flags |= FT_LOAD_NO_HINTING; + else + load_flags |= load_target; + + return load_flags; +} + +QFontEngineFT::Glyph *QFontEngineFT::loadGlyphMetrics(QGlyphSet *set, uint glyph, GlyphFormat format) const +{ + Glyph *g = set->getGlyph(glyph); + if (g && g->format == format) + return g; + + bool hsubpixel = false; + int vfactor = 1; + int load_flags = loadFlags(set, format, 0, hsubpixel, vfactor); + + // apply our matrix to this, but note that the metrics will not be affected by this. + FT_Face face = lockFace(); + FT_Matrix matrix = this->matrix; + FT_Matrix_Multiply(&set->transformationMatrix, &matrix); + FT_Set_Transform(face, &matrix, 0); + freetype->matrix = matrix; + + bool transform = matrix.xx != 0x10000 || matrix.yy != 0x10000 || matrix.xy != 0 || matrix.yx != 0; + if (transform) + load_flags |= FT_LOAD_NO_BITMAP; + + FT_Error err = FT_Load_Glyph(face, glyph, load_flags); + if (err && (load_flags & FT_LOAD_NO_BITMAP)) { + load_flags &= ~FT_LOAD_NO_BITMAP; + err = FT_Load_Glyph(face, glyph, load_flags); + } + if (err == FT_Err_Too_Few_Arguments) { + // this is an error in the bytecode interpreter, just try to run without it + load_flags |= FT_LOAD_FORCE_AUTOHINT; + err = FT_Load_Glyph(face, glyph, load_flags); + } + if (err != FT_Err_Ok) + qWarning("load glyph failed err=%x face=%p, glyph=%d", err, face, glyph); + + unlockFace(); + if (set->outline_drawing) + return 0; + + if (!g) { + g = new Glyph; + g->uploadedToServer = false; + g->data = 0; + } + + FT_GlyphSlot slot = face->glyph; + if (embolden) Q_FT_GLYPHSLOT_EMBOLDEN(slot); + int left = slot->metrics.horiBearingX; + int right = slot->metrics.horiBearingX + slot->metrics.width; + int top = slot->metrics.horiBearingY; + int bottom = slot->metrics.horiBearingY - slot->metrics.height; + if (transform && slot->format != FT_GLYPH_FORMAT_BITMAP) { // freetype doesn't apply the transformation on the metrics + int l, r, t, b; + FT_Vector vector; + vector.x = left; + vector.y = top; + FT_Vector_Transform(&vector, &matrix); + l = r = vector.x; + t = b = vector.y; + vector.x = right; + vector.y = top; + FT_Vector_Transform(&vector, &matrix); + if (l > vector.x) l = vector.x; + if (r < vector.x) r = vector.x; + if (t < vector.y) t = vector.y; + if (b > vector.y) b = vector.y; + vector.x = right; + vector.y = bottom; + FT_Vector_Transform(&vector, &matrix); + if (l > vector.x) l = vector.x; + if (r < vector.x) r = vector.x; + if (t < vector.y) t = vector.y; + if (b > vector.y) b = vector.y; + vector.x = left; + vector.y = bottom; + FT_Vector_Transform(&vector, &matrix); + if (l > vector.x) l = vector.x; + if (r < vector.x) r = vector.x; + if (t < vector.y) t = vector.y; + if (b > vector.y) b = vector.y; + left = l; + right = r; + top = t; + bottom = b; + } + left = FLOOR(left); + right = CEIL(right); + bottom = FLOOR(bottom); + top = CEIL(top); + + g->linearAdvance = face->glyph->linearHoriAdvance >> 10; + g->width = TRUNC(right-left); + g->height = TRUNC(top-bottom); + g->x = TRUNC(left); + g->y = TRUNC(top); + g->advance = TRUNC(ROUND(face->glyph->advance.x)); + g->format = Format_None; + + return g; +} + +QFontEngineFT::Glyph *QFontEngineFT::loadGlyph(QGlyphSet *set, uint glyph, + QFixed subPixelPosition, + GlyphFormat format, + bool fetchMetricsOnly) const +{ +// Q_ASSERT(freetype->lock == 1); + + bool uploadToServer = false; + if (format == Format_None) { + if (defaultFormat != Format_None) { + format = defaultFormat; + if (canUploadGlyphsToServer) + uploadToServer = true; + } else { + format = Format_Mono; + } + } + + Glyph *g = set->getGlyph(glyph, subPixelPosition); + if (g && g->format == format) { + if (uploadToServer && !g->uploadedToServer) { + set->setGlyph(glyph, subPixelPosition, 0); + delete g; + g = 0; + } else { + return g; + } + } + + QFontEngineFT::GlyphInfo info; + + Q_ASSERT(format != Format_None); + bool hsubpixel = false; + int vfactor = 1; + int load_flags = loadFlags(set, format, 0, hsubpixel, vfactor); + +#ifndef Q_WS_QWS + if (format != Format_Mono && !embeddedbitmap) + load_flags |= FT_LOAD_NO_BITMAP; +#endif + + FT_Matrix matrix = freetype->matrix; + bool transform = matrix.xx != 0x10000 + || matrix.yy != 0x10000 + || matrix.xy != 0 + || matrix.yx != 0; + + if (transform) + load_flags |= FT_LOAD_NO_BITMAP; + + FT_Face face = freetype->face; + + FT_Vector v; + v.x = format == Format_Mono ? 0 : FT_Pos(subPixelPosition.toReal() * 64); + v.y = 0; + FT_Set_Transform(face, &freetype->matrix, &v); + + FT_Error err = FT_Load_Glyph(face, glyph, load_flags); + if (err && (load_flags & FT_LOAD_NO_BITMAP)) { + load_flags &= ~FT_LOAD_NO_BITMAP; + err = FT_Load_Glyph(face, glyph, load_flags); + } + if (err == FT_Err_Too_Few_Arguments) { + // this is an error in the bytecode interpreter, just try to run without it + load_flags |= FT_LOAD_FORCE_AUTOHINT; + err = FT_Load_Glyph(face, glyph, load_flags); + } + if (err != FT_Err_Ok) + qWarning("load glyph failed err=%x face=%p, glyph=%d", err, face, glyph); + + if (set->outline_drawing && fetchMetricsOnly) + return 0; + + FT_GlyphSlot slot = face->glyph; + if (embolden) Q_FT_GLYPHSLOT_EMBOLDEN(slot); + FT_Library library = qt_getFreetype(); + + info.xOff = TRUNC(ROUND(slot->advance.x)); + info.yOff = 0; + + uchar *glyph_buffer = 0; + int glyph_buffer_size = 0; +#if defined(QT_USE_FREETYPE_LCDFILTER) + bool useFreetypeRenderGlyph = false; + if (slot->format == FT_GLYPH_FORMAT_OUTLINE && (hsubpixel || vfactor != 1)) { + err = FT_Library_SetLcdFilter(library, (FT_LcdFilter)lcdFilterType); + if (err == FT_Err_Ok) + useFreetypeRenderGlyph = true; + } + + if (useFreetypeRenderGlyph) { + err = FT_Render_Glyph(slot, hsubpixel ? FT_RENDER_MODE_LCD : FT_RENDER_MODE_LCD_V); + + if (err != FT_Err_Ok) + qWarning("render glyph failed err=%x face=%p, glyph=%d", err, face, glyph); + + FT_Library_SetLcdFilter(library, FT_LCD_FILTER_NONE); + + info.height = slot->bitmap.rows / vfactor; + info.width = hsubpixel ? slot->bitmap.width / 3 : slot->bitmap.width; + info.x = -slot->bitmap_left; + info.y = slot->bitmap_top; + + glyph_buffer_size = info.width * info.height * 4; + glyph_buffer = new uchar[glyph_buffer_size]; + + if (hsubpixel) + convertRGBToARGB(slot->bitmap.buffer, (uint *)glyph_buffer, info.width, info.height, slot->bitmap.pitch, subpixelType != QFontEngineFT::Subpixel_RGB, false); + else if (vfactor != 1) + convertRGBToARGB_V(slot->bitmap.buffer, (uint *)glyph_buffer, info.width, info.height, slot->bitmap.pitch, subpixelType != QFontEngineFT::Subpixel_VRGB, false); + } else +#endif + { + int left = slot->metrics.horiBearingX; + int right = slot->metrics.horiBearingX + slot->metrics.width; + int top = slot->metrics.horiBearingY; + int bottom = slot->metrics.horiBearingY - slot->metrics.height; + if(transform && slot->format != FT_GLYPH_FORMAT_BITMAP) { + int l, r, t, b; + FT_Vector vector; + vector.x = left; + vector.y = top; + FT_Vector_Transform(&vector, &matrix); + l = r = vector.x; + t = b = vector.y; + vector.x = right; + vector.y = top; + FT_Vector_Transform(&vector, &matrix); + if (l > vector.x) l = vector.x; + if (r < vector.x) r = vector.x; + if (t < vector.y) t = vector.y; + if (b > vector.y) b = vector.y; + vector.x = right; + vector.y = bottom; + FT_Vector_Transform(&vector, &matrix); + if (l > vector.x) l = vector.x; + if (r < vector.x) r = vector.x; + if (t < vector.y) t = vector.y; + if (b > vector.y) b = vector.y; + vector.x = left; + vector.y = bottom; + FT_Vector_Transform(&vector, &matrix); + if (l > vector.x) l = vector.x; + if (r < vector.x) r = vector.x; + if (t < vector.y) t = vector.y; + if (b > vector.y) b = vector.y; + left = l; + right = r; + top = t; + bottom = b; + } + left = FLOOR(left); + right = CEIL(right); + bottom = FLOOR(bottom); + top = CEIL(top); + + int hpixels = TRUNC(right - left); + // subpixel position requires one more pixel + if (subPixelPosition > 0 && format != Format_Mono) + hpixels++; + + if (hsubpixel) + hpixels = hpixels*3 + 8; + info.width = hpixels; + info.height = TRUNC(top - bottom); + info.x = -TRUNC(left); + info.y = TRUNC(top); + if (hsubpixel) { + info.width /= 3; + info.x += 1; + } + + bool large_glyph = (((short)(slot->linearHoriAdvance>>10) != slot->linearHoriAdvance>>10) + || ((uchar)(info.width) != info.width) + || ((uchar)(info.height) != info.height) + || ((signed char)(info.x) != info.x) + || ((signed char)(info.y) != info.y) + || ((signed char)(info.xOff) != info.xOff)); + + if (large_glyph) { + delete [] glyph_buffer; + return 0; + } + + int pitch = (format == Format_Mono ? ((info.width + 31) & ~31) >> 3 : + (format == Format_A8 ? (info.width + 3) & ~3 : info.width * 4)); + glyph_buffer_size = pitch * info.height; + glyph_buffer = new uchar[glyph_buffer_size]; + + if (slot->format == FT_GLYPH_FORMAT_OUTLINE) { + FT_Bitmap bitmap; + bitmap.rows = info.height*vfactor; + bitmap.width = hpixels; + bitmap.pitch = format == Format_Mono ? (((info.width + 31) & ~31) >> 3) : ((bitmap.width + 3) & ~3); + if (!hsubpixel && vfactor == 1) + bitmap.buffer = glyph_buffer; + else + bitmap.buffer = new uchar[bitmap.rows*bitmap.pitch]; + memset(bitmap.buffer, 0, bitmap.rows*bitmap.pitch); + bitmap.pixel_mode = format == Format_Mono ? FT_PIXEL_MODE_MONO : FT_PIXEL_MODE_GRAY; + FT_Matrix matrix; + matrix.xx = (hsubpixel ? 3 : 1) << 16; + matrix.yy = vfactor << 16; + matrix.yx = matrix.xy = 0; + + FT_Outline_Transform(&slot->outline, &matrix); + FT_Outline_Translate (&slot->outline, (hsubpixel ? -3*left +(4<<6) : -left), -bottom*vfactor); + FT_Outline_Get_Bitmap(library, &slot->outline, &bitmap); + if (hsubpixel) { + Q_ASSERT (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY); + Q_ASSERT(antialias); + uchar *convoluted = new uchar[bitmap.rows*bitmap.pitch]; + bool useLegacyLcdFilter = false; +#if defined(FC_LCD_FILTER) && defined(FT_LCD_FILTER_H) + useLegacyLcdFilter = (lcdFilterType == FT_LCD_FILTER_LEGACY); +#endif + uchar *buffer = bitmap.buffer; + if (!useLegacyLcdFilter) { + convoluteBitmap(bitmap.buffer, convoluted, bitmap.width, info.height, bitmap.pitch); + buffer = convoluted; + } + convertRGBToARGB(buffer + 1, (uint *)glyph_buffer, info.width, info.height, bitmap.pitch, subpixelType != QFontEngineFT::Subpixel_RGB, useLegacyLcdFilter); + delete [] convoluted; + } else if (vfactor != 1) { + convertRGBToARGB_V(bitmap.buffer, (uint *)glyph_buffer, info.width, info.height, bitmap.pitch, subpixelType != QFontEngineFT::Subpixel_VRGB, true); + } + + if (bitmap.buffer != glyph_buffer) + delete [] bitmap.buffer; + } else if (slot->format == FT_GLYPH_FORMAT_BITMAP) { + Q_ASSERT(slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO); + uchar *src = slot->bitmap.buffer; + uchar *dst = glyph_buffer; + int h = slot->bitmap.rows; + if (format == Format_Mono) { + int bytes = ((info.width + 7) & ~7) >> 3; + while (h--) { + memcpy (dst, src, bytes); + dst += pitch; + src += slot->bitmap.pitch; + } + } else { + if (hsubpixel) { + while (h--) { + uint *dd = (uint *)dst; + *dd++ = 0; + for (int x = 0; x < slot->bitmap.width; x++) { + uint a = ((src[x >> 3] & (0x80 >> (x & 7))) ? 0xffffff : 0x000000); + *dd++ = a; + } + *dd++ = 0; + dst += pitch; + src += slot->bitmap.pitch; + } + } else if (vfactor != 1) { + while (h--) { + uint *dd = (uint *)dst; + for (int x = 0; x < slot->bitmap.width; x++) { + uint a = ((src[x >> 3] & (0x80 >> (x & 7))) ? 0xffffff : 0x000000); + *dd++ = a; + } + dst += pitch; + src += slot->bitmap.pitch; + } + } else { + while (h--) { + for (int x = 0; x < slot->bitmap.width; x++) { + unsigned char a = ((src[x >> 3] & (0x80 >> (x & 7))) ? 0xff : 0x00); + dst[x] = a; + } + dst += pitch; + src += slot->bitmap.pitch; + } + } + } + } else { + qWarning("QFontEngine: Glyph neither outline nor bitmap format=%d", slot->format); + delete [] glyph_buffer; + return 0; + } + } + + + if (!g) { + g = new Glyph; + g->uploadedToServer = false; + g->data = 0; + } + + g->linearAdvance = slot->linearHoriAdvance >> 10; + g->width = info.width; + g->height = info.height; + g->x = -info.x; + g->y = info.y; + g->advance = info.xOff; + g->format = format; + delete [] g->data; + g->data = glyph_buffer; + + if (uploadToServer) { + uploadGlyphToServer(set, glyph, g, &info, glyph_buffer_size); + } + + set->setGlyph(glyph, subPixelPosition, g); + + return g; +} + +bool QFontEngineFT::uploadGlyphToServer(QGlyphSet *set, uint glyphid, Glyph *g, GlyphInfo *info, int glyphDataSize) const +{ + Q_UNUSED(set); + Q_UNUSED(glyphid); + Q_UNUSED(g); + Q_UNUSED(info); + Q_UNUSED(glyphDataSize); + return false; +} + +QFontEngine::FaceId QFontEngineFT::faceId() const +{ + return face_id; +} + +QFontEngine::Properties QFontEngineFT::properties() const +{ + Properties p = freetype->properties(); + if (p.postscriptName.isEmpty()) { + p.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(fontDef.family.toUtf8()); + } + + return freetype->properties(); +} + +QFixed QFontEngineFT::emSquareSize() const +{ + if (FT_IS_SCALABLE(freetype->face)) + return freetype->face->units_per_EM; + else + return freetype->face->size->metrics.y_ppem; +} + +bool QFontEngineFT::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + return freetype->getSfntTable(tag, buffer, length); +} + +int QFontEngineFT::synthesized() const +{ + int s = 0; + if ((fontDef.style != QFont::StyleNormal) && !(freetype->face->style_flags & FT_STYLE_FLAG_ITALIC)) + s = SynthesizedItalic; + if ((fontDef.weight == QFont::Bold) && !(freetype->face->style_flags & FT_STYLE_FLAG_BOLD)) + s |= SynthesizedBold; + if (fontDef.stretch != 100 && FT_IS_SCALABLE(freetype->face)) + s |= SynthesizedStretch; + return s; +} + +QFixed QFontEngineFT::ascent() const +{ + return QFixed::fromFixed(metrics.ascender); +} + +QFixed QFontEngineFT::descent() const +{ + // subtract a pixel to work around QFontMetrics's built-in + 1 + return QFixed::fromFixed(-metrics.descender - 64); +} + +QFixed QFontEngineFT::leading() const +{ + return QFixed::fromFixed(metrics.height - metrics.ascender + metrics.descender); +} + +QFixed QFontEngineFT::xHeight() const +{ + TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(freetype->face, ft_sfnt_os2); + if (os2 && os2->sxHeight) { + lockFace(); + QFixed answer = QFixed(os2->sxHeight*freetype->face->size->metrics.y_ppem)/freetype->face->units_per_EM; + unlockFace(); + return answer; + } + return QFontEngine::xHeight(); +} + +QFixed QFontEngineFT::averageCharWidth() const +{ + TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(freetype->face, ft_sfnt_os2); + if (os2 && os2->xAvgCharWidth) { + lockFace(); + QFixed answer = QFixed(os2->xAvgCharWidth*freetype->face->size->metrics.x_ppem)/freetype->face->units_per_EM; + unlockFace(); + return answer; + } + return QFontEngine::averageCharWidth(); +} + +qreal QFontEngineFT::maxCharWidth() const +{ + return metrics.max_advance >> 6; +} + +static const ushort char_table[] = { + 40, + 67, + 70, + 75, + 86, + 88, + 89, + 91, + 102, + 114, + 124, + 127, + 205, + 645, + 884, + 922, + 1070, + 12386 +}; + +static const int char_table_entries = sizeof(char_table)/sizeof(ushort); + + +qreal QFontEngineFT::minLeftBearing() const +{ + if (lbearing == SHRT_MIN) + (void) minRightBearing(); // calculates both + return lbearing.toReal(); +} + +qreal QFontEngineFT::minRightBearing() const +{ + if (rbearing == SHRT_MIN) { + lbearing = rbearing = 0; + const QChar *ch = (const QChar *)(const void*)char_table; + QGlyphLayoutArray glyphs; + int ng = char_table_entries; + stringToCMap(ch, char_table_entries, &glyphs, &ng, QTextEngine::GlyphIndicesOnly); + while (--ng) { + if (glyphs.glyphs[ng]) { + glyph_metrics_t gi = const_cast(this)->boundingBox(glyphs.glyphs[ng]); + lbearing = qMin(lbearing, gi.x); + rbearing = qMin(rbearing, (gi.xoff - gi.x - gi.width)); + } + } + } + return rbearing.toReal(); +} + +QFixed QFontEngineFT::lineThickness() const +{ + return line_thickness; +} + +QFixed QFontEngineFT::underlinePosition() const +{ + return underline_position; +} + +void QFontEngineFT::doKerning(QGlyphLayout *g, QTextEngine::ShaperFlags flags) const +{ + if (!kerning_pairs_loaded) { + kerning_pairs_loaded = true; + lockFace(); + if (freetype->face->size->metrics.x_ppem != 0) { + QFixed scalingFactor(freetype->face->units_per_EM/freetype->face->size->metrics.x_ppem); + unlockFace(); + const_cast(this)->loadKerningPairs(scalingFactor); + } else { + unlockFace(); + } + } + QFontEngine::doKerning(g, flags); +} + +QFontEngineFT::QGlyphSet *QFontEngineFT::loadTransformedGlyphSet(const QTransform &matrix) +{ + if (matrix.type() > QTransform::TxShear) + return 0; + + // FT_Set_Transform only supports scalable fonts + if (!FT_IS_SCALABLE(freetype->face)) + return 0; + + FT_Matrix m; + m.xx = FT_Fixed(matrix.m11() * 65536); + m.xy = FT_Fixed(-matrix.m21() * 65536); + m.yx = FT_Fixed(-matrix.m12() * 65536); + m.yy = FT_Fixed(matrix.m22() * 65536); + + QGlyphSet *gs = 0; + + for (int i = 0; i < transformedGlyphSets.count(); ++i) { + const QGlyphSet &g = transformedGlyphSets.at(i); + if (g.transformationMatrix.xx == m.xx + && g.transformationMatrix.xy == m.xy + && g.transformationMatrix.yx == m.yx + && g.transformationMatrix.yy == m.yy) { + + // found a match, move it to the front + transformedGlyphSets.move(i, 0); + gs = &transformedGlyphSets[0]; + break; + } + } + + if (!gs) { + // don't try to load huge fonts + bool draw_as_outline = fontDef.pixelSize * qSqrt(qAbs(matrix.det())) >= 64; + if (draw_as_outline) + return 0; + + // don't cache more than 10 transformations + if (transformedGlyphSets.count() >= 10) { + transformedGlyphSets.move(transformedGlyphSets.size() - 1, 0); + freeServerGlyphSet(transformedGlyphSets.at(0).id); + } else { + transformedGlyphSets.prepend(QGlyphSet()); + } + gs = &transformedGlyphSets[0]; + + gs->clear(); + + gs->id = allocateServerGlyphSet(); + + gs->transformationMatrix = m; + gs->outline_drawing = draw_as_outline; + } + + return gs; +} + +QFixed QFontEngineFT::subPixelPositionForX(QFixed x) +{ + int m_subPixelPositionCount = 4; + if (!supportsSubPixelPositions()) + return 0; + + QFixed subPixelPosition; + if (x != 0) { + subPixelPosition = x - x.floor(); + QFixed fraction = (subPixelPosition / QFixed::fromReal(1.0 / m_subPixelPositionCount)).floor(); + subPixelPosition = fraction / QFixed(m_subPixelPositionCount); + } + return subPixelPosition; +} + +bool QFontEngineFT::loadGlyphs(QGlyphSet *gs, const glyph_t *glyphs, int num_glyphs, + const QFixedPoint *positions, + GlyphFormat format) +{ + FT_Face face = 0; + + for (int i = 0; i < num_glyphs; ++i) { + QFixed spp = subPixelPositionForX(positions[i].x); + Glyph *glyph = gs->getGlyph(glyphs[i], spp); + if (glyph == 0 || glyph->format != format) { + if (!face) { + face = lockFace(); + FT_Matrix m = matrix; + FT_Matrix_Multiply(&gs->transformationMatrix, &m); + FT_Set_Transform(face, &m, 0); + freetype->matrix = m; + } + if (!loadGlyph(gs, glyphs[i], spp, format)) { + unlockFace(); + return false; + } + } + } + + if (face) + unlockFace(); + + return true; +} + +void QFontEngineFT::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) +{ + FT_Face face = lockFace(Unscaled); + FT_Set_Transform(face, 0, 0); + FT_Load_Glyph(face, glyph, FT_LOAD_NO_BITMAP); + + int left = face->glyph->metrics.horiBearingX; + int right = face->glyph->metrics.horiBearingX + face->glyph->metrics.width; + int top = face->glyph->metrics.horiBearingY; + int bottom = face->glyph->metrics.horiBearingY - face->glyph->metrics.height; + + QFixedPoint p; + p.x = 0; + p.y = 0; + + metrics->width = QFixed::fromFixed(right-left); + metrics->height = QFixed::fromFixed(top-bottom); + metrics->x = QFixed::fromFixed(left); + metrics->y = QFixed::fromFixed(-top); + metrics->xoff = QFixed::fromFixed(face->glyph->advance.x); + + if (!FT_IS_SCALABLE(freetype->face)) + QFreetypeFace::addBitmapToPath(face->glyph, p, path); + else + QFreetypeFace::addGlyphToPath(face, face->glyph, p, path, face->units_per_EM << 6, face->units_per_EM << 6); + + FT_Set_Transform(face, &freetype->matrix, 0); + unlockFace(); +} + +static inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +bool QFontEngineFT::canRender(const QChar *string, int len) +{ + FT_Face face = freetype->face; +#if 0 + if (_cmap != -1) { + lockFace(); + for ( int i = 0; i < len; i++ ) { + unsigned int uc = getChar(string, i, len); + if (!FcCharSetHasChar (_font->charset, uc) && getAdobeCharIndex(face, _cmap, uc) == 0) { + allExist = false; + break; + } + } + unlockFace(); + } else +#endif + { + for ( int i = 0; i < len; i++ ) { + unsigned int uc = getChar(string, i, len); + if (!FT_Get_Char_Index(face, uc)) + return false; + } + } + return true; +} + +void QFontEngineFT::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (!glyphs.numGlyphs) + return; + + if (FT_IS_SCALABLE(freetype->face)) { + QFontEngine::addOutlineToPath(x, y, glyphs, path, flags); + } else { + QVarLengthArray positions; + QVarLengthArray positioned_glyphs; + QTransform matrix; + matrix.translate(x, y); + getGlyphPositions(glyphs, matrix, flags, positioned_glyphs, positions); + + FT_Face face = lockFace(Unscaled); + for (int gl = 0; gl < glyphs.numGlyphs; gl++) { + FT_UInt glyph = positioned_glyphs[gl]; + FT_Load_Glyph(face, glyph, FT_LOAD_TARGET_MONO); + freetype->addBitmapToPath(face->glyph, positions[gl], path); + } + unlockFace(); + } +} + +void QFontEngineFT::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, + QPainterPath *path, QTextItem::RenderFlags) +{ + FT_Face face = lockFace(Unscaled); + + for (int gl = 0; gl < numGlyphs; gl++) { + FT_UInt glyph = glyphs[gl]; + + FT_Load_Glyph(face, glyph, FT_LOAD_NO_BITMAP); + + FT_GlyphSlot g = face->glyph; + if (g->format != FT_GLYPH_FORMAT_OUTLINE) + continue; + QFreetypeFace::addGlyphToPath(face, g, positions[gl], path, xsize, ysize); + } + unlockFace(); +} + +bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + +#if !defined(QT_NO_FONTCONFIG) + extern QMutex *qt_fontdatabase_mutex(); + QMutex *mtx = 0; +#endif + + bool mirrored = flags & QTextEngine::RightToLeft; + int glyph_pos = 0; + if (freetype->symbol_map) { + FT_Face face = freetype->face; + for ( int i = 0; i < len; ++i ) { + unsigned int uc = getChar(str, i, len); + glyphs->glyphs[glyph_pos] = uc < QFreetypeFace::cmapCacheSize ? freetype->cmapCache[uc] : 0; + if ( !glyphs->glyphs[glyph_pos] ) { + glyph_t glyph; +#if !defined(QT_NO_FONTCONFIG) + if (!mtx) { + mtx = qt_fontdatabase_mutex(); + mtx->lock(); + } + + if (freetype->charset != 0 && FcCharSetHasChar(freetype->charset, uc)) { +#else + if (false) { +#endif + redo0: + glyph = FT_Get_Char_Index(face, uc); + if (!glyph && (uc == 0xa0 || uc == 0x9)) { + uc = 0x20; + goto redo0; + } + } else { + FT_Set_Charmap(face, freetype->symbol_map); + glyph = FT_Get_Char_Index(face, uc); + FT_Set_Charmap(face, freetype->unicode_map); + } + glyphs->glyphs[glyph_pos] = glyph; + if (uc < QFreetypeFace::cmapCacheSize) + freetype->cmapCache[uc] = glyph; + } + ++glyph_pos; + } + } else { + FT_Face face = freetype->face; + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + if (mirrored) + uc = QChar::mirroredChar(uc); + glyphs->glyphs[glyph_pos] = uc < QFreetypeFace::cmapCacheSize ? freetype->cmapCache[uc] : 0; + if (!glyphs->glyphs[glyph_pos]) { +#if !defined(QT_NO_FONTCONFIG) + if (!mtx) { + mtx = qt_fontdatabase_mutex(); + mtx->lock(); + } + + if (freetype->charset == 0 || FcCharSetHasChar(freetype->charset, uc)) +#endif + { + redo: + glyph_t glyph = FT_Get_Char_Index(face, uc); + if (!glyph && (uc == 0xa0 || uc == 0x9)) { + uc = 0x20; + goto redo; + } + glyphs->glyphs[glyph_pos] = glyph; + if (uc < QFreetypeFace::cmapCacheSize) + freetype->cmapCache[uc] = glyph; + } + } + ++glyph_pos; + } + } + + *nglyphs = glyph_pos; + glyphs->numGlyphs = glyph_pos; + +#if !defined(QT_NO_FONTCONFIG) + if (mtx) + mtx->unlock(); +#endif + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + recalcAdvances(glyphs, flags); + + return true; +} + +void QFontEngineFT::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + FT_Face face = 0; + bool design = (default_hint_style == HintNone || + default_hint_style == HintLight || + (flags & HB_ShaperFlag_UseDesignMetrics)); + for (int i = 0; i < glyphs->numGlyphs; i++) { + Glyph *g = defaultGlyphSet.getGlyph(glyphs->glyphs[i]); + if (g) { + glyphs->advances_x[i] = design ? QFixed::fromFixed(g->linearAdvance) : QFixed(g->advance); + } else { + if (!face) + face = lockFace(); + g = loadGlyph(glyphs->glyphs[i], 0, Format_None, true); + glyphs->advances_x[i] = design ? QFixed::fromFixed(face->glyph->linearHoriAdvance >> 10) + : QFixed::fromFixed(face->glyph->metrics.horiAdvance).round(); + } + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + glyphs->advances_x[i] = glyphs->advances_x[i].round(); + glyphs->advances_y[i] = 0; + } + if (face) + unlockFace(); +} + +glyph_metrics_t QFontEngineFT::boundingBox(const QGlyphLayout &glyphs) +{ + + FT_Face face = 0; + + glyph_metrics_t overall; + // initialize with line height, we get the same behaviour on all platforms + overall.y = -ascent(); + overall.height = ascent() + descent() + 1; + + QFixed ymax = 0; + QFixed xmax = 0; + for (int i = 0; i < glyphs.numGlyphs; i++) { + Glyph *g = defaultGlyphSet.getGlyph(glyphs.glyphs[i]); + if (!g) { + if (!face) + face = lockFace(); + g = loadGlyph(glyphs.glyphs[i], 0, Format_None, true); + } + if (g) { + QFixed x = overall.xoff + glyphs.offsets[i].x + g->x; + QFixed y = overall.yoff + glyphs.offsets[i].y - g->y; + overall.x = qMin(overall.x, x); + overall.y = qMin(overall.y, y); + xmax = qMax(xmax, x + g->width); + ymax = qMax(ymax, y + g->height); + overall.xoff += qRound(g->advance); + } else { + int left = FLOOR(face->glyph->metrics.horiBearingX); + int right = CEIL(face->glyph->metrics.horiBearingX + face->glyph->metrics.width); + int top = CEIL(face->glyph->metrics.horiBearingY); + int bottom = FLOOR(face->glyph->metrics.horiBearingY - face->glyph->metrics.height); + + QFixed x = overall.xoff + glyphs.offsets[i].x - (-TRUNC(left)); + QFixed y = overall.yoff + glyphs.offsets[i].y - TRUNC(top); + overall.x = qMin(overall.x, x); + overall.y = qMin(overall.y, y); + xmax = qMax(xmax, x + TRUNC(right - left)); + ymax = qMax(ymax, y + TRUNC(top - bottom)); + overall.xoff += qRound(TRUNC(ROUND(face->glyph->advance.x))); + } + } + overall.height = qMax(overall.height, ymax - overall.y); + overall.width = xmax - overall.x; + + if (face) + unlockFace(); + + return overall; +} + +glyph_metrics_t QFontEngineFT::boundingBox(glyph_t glyph) +{ + FT_Face face = 0; + glyph_metrics_t overall; + Glyph *g = defaultGlyphSet.getGlyph(glyph); + if (!g) { + face = lockFace(); + g = loadGlyph(glyph, 0, Format_None, true); + } + if (g) { + overall.x = g->x; + overall.y = -g->y; + overall.width = g->width; + overall.height = g->height; + overall.xoff = g->advance; + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + overall.xoff = overall.xoff.round(); + } else { + int left = FLOOR(face->glyph->metrics.horiBearingX); + int right = CEIL(face->glyph->metrics.horiBearingX + face->glyph->metrics.width); + int top = CEIL(face->glyph->metrics.horiBearingY); + int bottom = FLOOR(face->glyph->metrics.horiBearingY - face->glyph->metrics.height); + + overall.width = TRUNC(right-left); + overall.height = TRUNC(top-bottom); + overall.x = TRUNC(left); + overall.y = -TRUNC(top); + overall.xoff = TRUNC(ROUND(face->glyph->advance.x)); + } + if (face) + unlockFace(); + return overall; +} + +glyph_metrics_t QFontEngineFT::boundingBox(glyph_t glyph, const QTransform &matrix) +{ + return alphaMapBoundingBox(glyph, 0, matrix, QFontEngine::Format_None); +} + +glyph_metrics_t QFontEngineFT::alphaMapBoundingBox(glyph_t glyph, QFixed subPixelPosition, const QTransform &matrix, QFontEngine::GlyphFormat format) +{ + FT_Face face = 0; + glyph_metrics_t overall; + QGlyphSet *glyphSet = 0; + if (matrix.type() > QTransform::TxTranslate && FT_IS_SCALABLE(freetype->face)) { + // TODO move everything here to a method of its own to access glyphSets + // to be shared with a new method that will replace loadTransformedGlyphSet() + FT_Matrix m; + m.xx = FT_Fixed(matrix.m11() * 65536); + m.xy = FT_Fixed(-matrix.m21() * 65536); + m.yx = FT_Fixed(-matrix.m12() * 65536); + m.yy = FT_Fixed(matrix.m22() * 65536); + for (int i = 0; i < transformedGlyphSets.count(); ++i) { + const QGlyphSet &g = transformedGlyphSets.at(i); + if (g.transformationMatrix.xx == m.xx + && g.transformationMatrix.xy == m.xy + && g.transformationMatrix.yx == m.yx + && g.transformationMatrix.yy == m.yy) { + + // found a match, move it to the front + transformedGlyphSets.move(i, 0); + glyphSet = &transformedGlyphSets[0]; + break; + } + } + + if (!glyphSet) { + // don't cache more than 10 transformations + if (transformedGlyphSets.count() >= 10) { + transformedGlyphSets.move(transformedGlyphSets.size() - 1, 0); + freeServerGlyphSet(transformedGlyphSets.at(0).id); + } else { + transformedGlyphSets.prepend(QGlyphSet()); + } + glyphSet = &transformedGlyphSets[0]; + glyphSet->clear(); + glyphSet->id = allocateServerGlyphSet(); + glyphSet->transformationMatrix = m; + } + Q_ASSERT(glyphSet); + } else { + glyphSet = &defaultGlyphSet; + } + Glyph * g = glyphSet->getGlyph(glyph); + if (!g || g->format != format) { + face = lockFace(); + FT_Matrix m = this->matrix; + FT_Matrix_Multiply(&glyphSet->transformationMatrix, &m); + freetype->matrix = m; + g = loadGlyph(glyphSet, glyph, subPixelPosition, format); + } + + if (g) { + overall.x = g->x; + overall.y = -g->y; + overall.width = g->width; + overall.height = g->height; + overall.xoff = g->advance; + } else { + int left = FLOOR(face->glyph->metrics.horiBearingX); + int right = CEIL(face->glyph->metrics.horiBearingX + face->glyph->metrics.width); + int top = CEIL(face->glyph->metrics.horiBearingY); + int bottom = FLOOR(face->glyph->metrics.horiBearingY - face->glyph->metrics.height); + + overall.width = TRUNC(right-left); + overall.height = TRUNC(top-bottom); + overall.x = TRUNC(left); + overall.y = -TRUNC(top); + overall.xoff = TRUNC(ROUND(face->glyph->advance.x)); + } + if (face) + unlockFace(); + return overall; +} + +QImage QFontEngineFT::alphaMapForGlyph(glyph_t g, QFixed subPixelPosition) +{ + lockFace(); + + GlyphFormat glyph_format = antialias ? Format_A8 : Format_Mono; + + Glyph *glyph = defaultGlyphSet.outline_drawing ? 0 : loadGlyph(g, subPixelPosition, glyph_format); + if (!glyph) { + unlockFace(); + return QFontEngine::alphaMapForGlyph(g); + } + + const int pitch = antialias ? (glyph->width + 3) & ~3 : ((glyph->width + 31)/32) * 4; + + QImage img(glyph->width, glyph->height, antialias ? QImage::Format_Indexed8 : QImage::Format_Mono); + if (antialias) { + QVector colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + img.setColorTable(colors); + } else { + QVector colors(2); + colors[0] = qRgba(0, 0, 0, 0); + colors[1] = qRgba(0, 0, 0, 255); + img.setColorTable(colors); + } + Q_ASSERT(img.bytesPerLine() == pitch); + if (glyph->width) { + for (int y = 0; y < glyph->height; ++y) + memcpy(img.scanLine(y), &glyph->data[y * pitch], pitch); + } + unlockFace(); + + return img; +} + +QImage QFontEngineFT::alphaRGBMapForGlyph(glyph_t g, QFixed subPixelPosition, int margin, const QTransform &t) +{ + if (t.type() > QTransform::TxTranslate) + return QFontEngine::alphaRGBMapForGlyph(g, subPixelPosition, margin, t); + + lockFace(); + + GlyphFormat glyph_format = Format_A32; + + Glyph *glyph = defaultGlyphSet.outline_drawing ? 0 : loadGlyph(g, subPixelPosition, glyph_format); + if (!glyph) { + unlockFace(); + return QFontEngine::alphaRGBMapForGlyph(g, subPixelPosition, margin, t); + } + + QImage img(glyph->width, glyph->height, QImage::Format_RGB32); + memcpy(img.bits(), glyph->data, 4 * glyph->width * glyph->height); + unlockFace(); + + return img; +} + +void QFontEngineFT::removeGlyphFromCache(glyph_t glyph) +{ + defaultGlyphSet.removeGlyphFromCache(glyph, 0); +} + +int QFontEngineFT::glyphCount() const +{ + int count = 0; + FT_Face face = lockFace(); + if (face) { + count = face->num_glyphs; + unlockFace(); + } + return count; +} + +FT_Face QFontEngineFT::lockFace(Scaling scale) const +{ + freetype->lock(); + FT_Face face = freetype->face; + if (scale == Unscaled) { + FT_Set_Char_Size(face, face->units_per_EM << 6, face->units_per_EM << 6, 0, 0); + freetype->xsize = face->units_per_EM << 6; + freetype->ysize = face->units_per_EM << 6; + } else if (freetype->xsize != xsize || freetype->ysize != ysize) { + FT_Set_Char_Size(face, xsize, ysize, 0, 0); + freetype->xsize = xsize; + freetype->ysize = ysize; + } + if (freetype->matrix.xx != matrix.xx || + freetype->matrix.yy != matrix.yy || + freetype->matrix.xy != matrix.xy || + freetype->matrix.yx != matrix.yx) { + freetype->matrix = matrix; + FT_Set_Transform(face, &freetype->matrix, 0); + } + + return face; +} + +void QFontEngineFT::unlockFace() const +{ + freetype->unlock(); +} + +FT_Face QFontEngineFT::non_locked_face() const +{ + return freetype->face; +} + + +QFontEngineFT::QGlyphSet::QGlyphSet() + : id(0), outline_drawing(false) +{ + transformationMatrix.xx = 0x10000; + transformationMatrix.yy = 0x10000; + transformationMatrix.xy = 0; + transformationMatrix.yx = 0; + memset(fast_glyph_data, 0, sizeof(fast_glyph_data)); + fast_glyph_count = 0; +} + +QFontEngineFT::QGlyphSet::~QGlyphSet() +{ + clear(); +} + +void QFontEngineFT::QGlyphSet::clear() +{ + if (fast_glyph_count > 0) { + for (int i = 0; i < 256; ++i) { + if (fast_glyph_data[i]) { + delete fast_glyph_data[i]; + fast_glyph_data[i] = 0; + } + } + fast_glyph_count = 0; + } + qDeleteAll(glyph_data); + glyph_data.clear(); +} + +void QFontEngineFT::QGlyphSet::removeGlyphFromCache(glyph_t index, QFixed subPixelPosition) +{ + if (useFastGlyphData(index, subPixelPosition)) { + if (fast_glyph_data[index]) { + delete fast_glyph_data[index]; + fast_glyph_data[index] = 0; + if (fast_glyph_count > 0) + --fast_glyph_count; + } + } else { + delete glyph_data.take(GlyphAndSubPixelPosition(index, subPixelPosition)); + } +} + +void QFontEngineFT::QGlyphSet::setGlyph(glyph_t index, QFixed subPixelPosition, Glyph *glyph) +{ + if (useFastGlyphData(index, subPixelPosition)) { + if (!fast_glyph_data[index]) + ++fast_glyph_count; + fast_glyph_data[index] = glyph; + } else { + glyph_data.insert(GlyphAndSubPixelPosition(index, subPixelPosition), glyph); + } +} + +unsigned long QFontEngineFT::allocateServerGlyphSet() +{ + return 0; +} + +void QFontEngineFT::freeServerGlyphSet(unsigned long id) +{ + Q_UNUSED(id); +} + +HB_Error QFontEngineFT::getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints) +{ + lockFace(); + bool hsubpixel = true; + int vfactor = 1; + int load_flags = loadFlags(0, Format_A8, flags, hsubpixel, vfactor); + HB_Error result = freetype->getPointInOutline(glyph, load_flags, point, xpos, ypos, nPoints); + unlockFace(); + return result; +} + +QT_END_NAMESPACE + +#endif // QT_NO_FREETYPE diff --git a/src/gui/text/qfontengine_ft_p.h b/src/gui/text/qfontengine_ft_p.h new file mode 100644 index 0000000000..887efed843 --- /dev/null +++ b/src/gui/text/qfontengine_ft_p.h @@ -0,0 +1,380 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QFONTENGINE_FT_P_H +#define QFONTENGINE_FT_P_H +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qfontengine_p.h" + +#ifndef QT_NO_FREETYPE + +#include +#include FT_FREETYPE_H + +#if defined(Q_WS_X11) +#include +#endif + +#include + +#ifndef QT_NO_FONTCONFIG +#include +#endif + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QFontEngineFTRawFont; + +/* + * This struct represents one font file on disk (like Arial.ttf) and is shared between all the font engines + * that show this font file (at different pixel sizes). + */ +struct QFreetypeFace +{ + void computeSize(const QFontDef &fontDef, int *xsize, int *ysize, bool *outline_drawing); + QFontEngine::Properties properties() const; + bool getSfntTable(uint tag, uchar *buffer, uint *length) const; + + static QFreetypeFace *getFace(const QFontEngine::FaceId &face_id, + const QByteArray &fontData = QByteArray()); + void release(const QFontEngine::FaceId &face_id); + + // locks the struct for usage. Any read/write operations require locking. + void lock() + { + _lock.lock(); + } + void unlock() + { + _lock.unlock(); + } + + FT_Face face; + HB_Face hbFace; +#ifndef QT_NO_FONTCONFIG + FcCharSet *charset; +#endif + int xsize; // 26.6 + int ysize; // 26.6 + FT_Matrix matrix; + FT_CharMap unicode_map; + FT_CharMap symbol_map; + + enum { cmapCacheSize = 0x200 }; + glyph_t cmapCache[cmapCacheSize]; + + int fsType() const; + + HB_Error getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints); + + static void addGlyphToPath(FT_Face face, FT_GlyphSlot g, const QFixedPoint &point, QPainterPath *path, FT_Fixed x_scale, FT_Fixed y_scale); + static void addBitmapToPath(FT_GlyphSlot slot, const QFixedPoint &point, QPainterPath *path, bool = false); + +private: + friend class QFontEngineFTRawFont; + friend class QScopedPointerDeleter; + QFreetypeFace() : _lock(QMutex::Recursive) {} + ~QFreetypeFace() {} + QAtomicInt ref; + QMutex _lock; + QByteArray fontData; +}; + +class Q_GUI_EXPORT QFontEngineFT : public QFontEngine +{ +public: + + /* we don't cache glyphs that are too large anyway, so we can make this struct rather small */ + struct Glyph { + ~Glyph(); + short linearAdvance; + unsigned char width; + unsigned char height; + signed char x; + signed char y; + signed char advance; + signed char format; + uchar *data; + unsigned int uploadedToServer : 1; + }; + + enum SubpixelAntialiasingType { + Subpixel_None, + Subpixel_RGB, + Subpixel_BGR, + Subpixel_VRGB, + Subpixel_VBGR + }; + +#if defined(Q_WS_X11) && !defined(QT_NO_XRENDER) + typedef XGlyphInfo GlyphInfo; +#else + struct GlyphInfo { + unsigned short width; + unsigned short height; + short x; + short y; + short xOff; + short yOff; + }; +#endif + + struct GlyphAndSubPixelPosition + { + GlyphAndSubPixelPosition(glyph_t g, QFixed spp) : glyph(g), subPixelPosition(spp) {} + + bool operator==(const GlyphAndSubPixelPosition &other) const + { + return glyph == other.glyph && subPixelPosition == other.subPixelPosition; + } + + glyph_t glyph; + QFixed subPixelPosition; + }; + + struct QGlyphSet + { + QGlyphSet(); + ~QGlyphSet(); + FT_Matrix transformationMatrix; + unsigned long id; // server sided id, GlyphSet for X11 + bool outline_drawing; + + void removeGlyphFromCache(glyph_t index, QFixed subPixelPosition); + void clear(); + inline bool useFastGlyphData(glyph_t index, QFixed subPixelPosition) const { + return (index < 256 && subPixelPosition == 0); + } + inline Glyph *getGlyph(glyph_t index, QFixed subPixelPosition = 0) const + { + if (useFastGlyphData(index, subPixelPosition)) + return fast_glyph_data[index]; + return glyph_data.value(GlyphAndSubPixelPosition(index, subPixelPosition)); + } + void setGlyph(glyph_t index, QFixed spp, Glyph *glyph); + +private: + mutable QHash glyph_data; // maps from glyph index to glyph data + mutable Glyph *fast_glyph_data[256]; // for fast lookup of glyphs < 256 + mutable int fast_glyph_count; + }; + + virtual QFontEngine::FaceId faceId() const; + virtual QFontEngine::Properties properties() const; + virtual QFixed emSquareSize() const; + virtual bool supportsSubPixelPositions() const + { + return default_hint_style == HintLight || + default_hint_style == HintNone; + } + + virtual bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + virtual int synthesized() const; + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual QFixed averageCharWidth() const; + + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const; + virtual qreal minRightBearing() const; + virtual QFixed lineThickness() const; + virtual QFixed underlinePosition() const; + + void doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + inline virtual Type type() const + { return QFontEngine::Freetype; } + inline virtual const char *name() const + { return "freetype"; } + + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + + virtual bool canRender(const QChar *string, int len); + + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, + QPainterPath *path, QTextItem::RenderFlags flags); + virtual void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, + QPainterPath *path, QTextItem::RenderFlags flags); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags) const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + virtual glyph_metrics_t boundingBox(glyph_t glyph, const QTransform &matrix); + + virtual void recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const; + virtual QImage alphaMapForGlyph(glyph_t g) { return alphaMapForGlyph(g, 0); } + virtual QImage alphaMapForGlyph(glyph_t, QFixed); + virtual QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, int margin, const QTransform &t); + virtual glyph_metrics_t alphaMapBoundingBox(glyph_t glyph, + QFixed subPixelPosition, + const QTransform &matrix, + QFontEngine::GlyphFormat format); + virtual void removeGlyphFromCache(glyph_t glyph); + + virtual int glyphCount() const; + + enum Scaling { + Scaled, + Unscaled + }; + FT_Face lockFace(Scaling scale = Scaled) const; + void unlockFace() const; + + FT_Face non_locked_face() const; + + inline bool drawAntialiased() const { return antialias; } + inline bool invalid() const { return xsize == 0 && ysize == 0; } + inline bool isBitmapFont() const { return defaultFormat == Format_Mono; } + + inline Glyph *loadGlyph(uint glyph, QFixed subPixelPosition, GlyphFormat format = Format_None, bool fetchMetricsOnly = false) const + { return loadGlyph(&defaultGlyphSet, glyph, subPixelPosition, format, fetchMetricsOnly); } + Glyph *loadGlyph(QGlyphSet *set, uint glyph, QFixed subPixelPosition, GlyphFormat = Format_None, bool fetchMetricsOnly = false) const; + + QGlyphSet *defaultGlyphs() { return &defaultGlyphSet; } + GlyphFormat defaultGlyphFormat() const { return defaultFormat; } + + inline Glyph *cachedGlyph(glyph_t g) const { return defaultGlyphSet.getGlyph(g, 0); } + + QGlyphSet *loadTransformedGlyphSet(const QTransform &matrix); + QFixed subPixelPositionForX(QFixed x); + bool loadGlyphs(QGlyphSet *gs, const glyph_t *glyphs, int num_glyphs, + const QFixedPoint *positions, + GlyphFormat format = Format_Render); + +#if defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN) + virtual void draw(QPaintEngine * /*p*/, qreal /*x*/, qreal /*y*/, const QTextItemInt & /*si*/) {} +#endif + + QFontEngineFT(const QFontDef &fd); + virtual ~QFontEngineFT(); + + bool init(FaceId faceId, bool antiaalias, GlyphFormat defaultFormat = Format_None, + const QByteArray &fontData = QByteArray()); + bool init(FaceId faceId, bool antialias, GlyphFormat format, + QFreetypeFace *freetypeFace); + + virtual HB_Error getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints); + + enum HintStyle { + HintNone, + HintLight, + HintMedium, + HintFull + }; + + void setDefaultHintStyle(HintStyle style); + HintStyle defaultHintStyle() const { return default_hint_style; } +protected: + + void freeGlyphSets(); + + virtual bool uploadGlyphToServer(QGlyphSet *set, uint glyphid, Glyph *g, GlyphInfo *info, int glyphDataSize) const; + virtual unsigned long allocateServerGlyphSet(); + virtual void freeServerGlyphSet(unsigned long id); + + QFreetypeFace *freetype; + int default_load_flags; + + + HintStyle default_hint_style; + + bool antialias; + bool transform; + bool embolden; + SubpixelAntialiasingType subpixelType; + int lcdFilterType; + bool canUploadGlyphsToServer; + bool embeddedbitmap; + +private: + friend class QFontEngineFTRawFont; + + QFontEngineFT::Glyph *loadGlyphMetrics(QGlyphSet *set, uint glyph, GlyphFormat format) const; + int loadFlags(QGlyphSet *set, GlyphFormat format, int flags, bool &hsubpixel, int &vfactor) const; + + GlyphFormat defaultFormat; + FT_Matrix matrix; + + QList transformedGlyphSets; + mutable QGlyphSet defaultGlyphSet; + + QFontEngine::FaceId face_id; + + int xsize; + int ysize; + + mutable QFixed lbearing; + mutable QFixed rbearing; + QFixed line_thickness; + QFixed underline_position; + + FT_Size_Metrics metrics; + mutable bool kerning_pairs_loaded; +}; + +inline uint qHash(const QFontEngineFT::GlyphAndSubPixelPosition &g) +{ + return (g.glyph << 8) | (g.subPixelPosition * 10).round().toInt(); +} + +QT_END_NAMESPACE + +#endif // QT_NO_FREETYPE + +#endif // QFONTENGINE_FT_P_H diff --git a/src/gui/text/qfontengine_mac.mm b/src/gui/text/qfontengine_mac.mm new file mode 100644 index 0000000000..673a7c86fa --- /dev/null +++ b/src/gui/text/qfontengine_mac.mm @@ -0,0 +1,1236 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontengine_mac_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + QFontEngine debug facilities + *****************************************************************************/ +//#define DEBUG_ADVANCES + +extern int qt_antialiasing_threshold; // QApplication.cpp + +#ifndef FixedToQFixed +#define FixedToQFixed(a) QFixed::fromFixed((a) >> 10) +#define QFixedToFixed(x) ((x).value() << 10) +#endif + +class QMacFontPath +{ + float x, y; + QPainterPath *path; +public: + inline QMacFontPath(float _x, float _y, QPainterPath *_path) : x(_x), y(_y), path(_path) { } + inline void setPosition(float _x, float _y) { x = _x; y = _y; } + inline void advance(float _x) { x += _x; } + static OSStatus lineTo(const Float32Point *, void *); + static OSStatus cubicTo(const Float32Point *, const Float32Point *, + const Float32Point *, void *); + static OSStatus moveTo(const Float32Point *, void *); + static OSStatus closePath(void *); +}; + +OSStatus QMacFontPath::lineTo(const Float32Point *pt, void *data) + +{ + QMacFontPath *p = static_cast(data); + p->path->lineTo(p->x + pt->x, p->y + pt->y); + return noErr; +} + +OSStatus QMacFontPath::cubicTo(const Float32Point *cp1, const Float32Point *cp2, + const Float32Point *ep, void *data) + +{ + QMacFontPath *p = static_cast(data); + p->path->cubicTo(p->x + cp1->x, p->y + cp1->y, + p->x + cp2->x, p->y + cp2->y, + p->x + ep->x, p->y + ep->y); + return noErr; +} + +OSStatus QMacFontPath::moveTo(const Float32Point *pt, void *data) +{ + QMacFontPath *p = static_cast(data); + p->path->moveTo(p->x + pt->x, p->y + pt->y); + return noErr; +} + +OSStatus QMacFontPath::closePath(void *data) +{ + static_cast(data)->path->closeSubpath(); + return noErr; +} + + +#ifndef QT_MAC_USE_COCOA +QFontEngineMacMulti::QFontEngineMacMulti(const ATSFontFamilyRef &atsFamily, const ATSFontRef &atsFontRef, const QFontDef &fontDef, bool kerning) + : QFontEngineMulti(0) +{ + this->fontDef = fontDef; + this->kerning = kerning; + + // hopefully (CTFontCreateWithName or CTFontCreateWithFontDescriptor) + CTFontCreateCopyWithSymbolicTraits + // (or CTFontCreateWithQuickdrawInstance) + FMFontFamily fmFamily; + FMFontStyle fntStyle = 0; + fmFamily = FMGetFontFamilyFromATSFontFamilyRef(atsFamily); + if (fmFamily == kInvalidFontFamily) { + // Use the ATSFont then... + fontID = FMGetFontFromATSFontRef(atsFontRef); + } else { + if (fontDef.weight >= QFont::Bold) + fntStyle |= ::bold; + if (fontDef.style != QFont::StyleNormal) + fntStyle |= ::italic; + + FMFontStyle intrinsicStyle; + FMFont fnt = 0; + if (FMGetFontFromFontFamilyInstance(fmFamily, fntStyle, &fnt, &intrinsicStyle) == noErr) + fontID = FMGetATSFontRefFromFont(fnt); + } + + // CFDictionaryRef, + OSStatus status; + + status = ATSUCreateTextLayout(&textLayout); + Q_ASSERT(status == noErr); + + const int maxAttributeCount = 5; + ATSUAttributeTag tags[maxAttributeCount + 1]; + ByteCount sizes[maxAttributeCount + 1]; + ATSUAttributeValuePtr values[maxAttributeCount + 1]; + int attributeCount = 0; + + Fixed size = FixRatio(fontDef.pixelSize, 1); + tags[attributeCount] = kATSUSizeTag; + sizes[attributeCount] = sizeof(size); + values[attributeCount] = &size; + ++attributeCount; + + tags[attributeCount] = kATSUFontTag; + sizes[attributeCount] = sizeof(fontID); + values[attributeCount] = &this->fontID; + ++attributeCount; + + transform = CGAffineTransformIdentity; + if (fontDef.stretch != 100) { + transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1); + tags[attributeCount] = kATSUFontMatrixTag; + sizes[attributeCount] = sizeof(transform); + values[attributeCount] = &transform; + ++attributeCount; + } + + status = ATSUCreateStyle(&style); + Q_ASSERT(status == noErr); + + Q_ASSERT(attributeCount < maxAttributeCount + 1); + status = ATSUSetAttributes(style, attributeCount, tags, sizes, values); + Q_ASSERT(status == noErr); + + QFontEngineMac *fe = new QFontEngineMac(style, fontID, fontDef, this); + fe->ref.ref(); + engines.append(fe); +} + +QFontEngineMacMulti::~QFontEngineMacMulti() +{ + ATSUDisposeTextLayout(textLayout); + ATSUDisposeStyle(style); + + for (int i = 0; i < engines.count(); ++i) { + QFontEngineMac *fe = const_cast(static_cast(engines.at(i))); + fe->multiEngine = 0; + if (!fe->ref.deref()) + delete fe; + } + engines.clear(); +} + +struct QGlyphLayoutInfo +{ + QGlyphLayout *glyphs; + int *numGlyphs; + bool callbackCalled; + int *mappedFonts; + QTextEngine::ShaperFlags flags; + QFontEngineMacMulti::ShaperItem *shaperItem; + unsigned int styleStrategy; +}; + +static OSStatus atsuPostLayoutCallback(ATSULayoutOperationSelector selector, ATSULineRef lineRef, URefCon refCon, + void *operationExtraParameter, ATSULayoutOperationCallbackStatus *callbackStatus) +{ + Q_UNUSED(selector); + Q_UNUSED(operationExtraParameter); + + QGlyphLayoutInfo *nfo = reinterpret_cast(refCon); + nfo->callbackCalled = true; + + ATSLayoutRecord *layoutData = 0; + ItemCount itemCount = 0; + + OSStatus e = noErr; + e = ATSUDirectGetLayoutDataArrayPtrFromLineRef(lineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, + /*iCreate =*/ false, + (void **) &layoutData, + &itemCount); + if (e != noErr) + return e; + + *nfo->numGlyphs = itemCount - 1; + + Fixed *baselineDeltas = 0; + + e = ATSUDirectGetLayoutDataArrayPtrFromLineRef(lineRef, kATSUDirectDataBaselineDeltaFixedArray, + /*iCreate =*/ true, + (void **) &baselineDeltas, + &itemCount); + if (e != noErr) + return e; + + int nextCharStop = -1; + int currentClusterGlyph = -1; // first glyph in log cluster + QFontEngineMacMulti::ShaperItem *item = nfo->shaperItem; + if (item->charAttributes) { + item = nfo->shaperItem; +#if !defined(QT_NO_DEBUG) + int surrogates = 0; + const QChar *str = item->string; + for (int i = item->from; i < item->from + item->length - 1; ++i) { + surrogates += (str[i].unicode() >= 0xd800 && str[i].unicode() < 0xdc00 + && str[i+1].unicode() >= 0xdc00 && str[i+1].unicode() < 0xe000); + } +#endif + for (nextCharStop = item->from; nextCharStop < item->from + item->length; ++nextCharStop) + if (item->charAttributes[nextCharStop].charStop) + break; + nextCharStop -= item->from; + } + + nfo->glyphs->attributes[0].clusterStart = true; + int glyphIdx = 0; + int glyphIncrement = 1; + if (nfo->flags & QTextEngine::RightToLeft) { + glyphIdx = itemCount - 2; + glyphIncrement = -1; + } + for (int i = 0; i < *nfo->numGlyphs; ++i, glyphIdx += glyphIncrement) { + + int charOffset = layoutData[glyphIdx].originalOffset / sizeof(UniChar); + const int fontIdx = nfo->mappedFonts[charOffset]; + + ATSGlyphRef glyphId = layoutData[glyphIdx].glyphID; + + QFixed yAdvance = FixedToQFixed(baselineDeltas[glyphIdx]); + QFixed xAdvance = FixedToQFixed(layoutData[glyphIdx + 1].realPos - layoutData[glyphIdx].realPos); + + if (nfo->styleStrategy & QFont::ForceIntegerMetrics) { + yAdvance = yAdvance.round(); + xAdvance = xAdvance.round(); + } + + if (glyphId != 0xffff || i == 0) { + if (i < nfo->glyphs->numGlyphs) + { + nfo->glyphs->glyphs[i] = (glyphId & 0x00ffffff) | (fontIdx << 24); + + nfo->glyphs->advances_y[i] = yAdvance; + nfo->glyphs->advances_x[i] = xAdvance; + } + } else { + // ATSUI gives us 0xffff as glyph id at the index in the glyph array for + // a character position that maps to a ligtature. Such a glyph id does not + // result in any visual glyph, but it may have an advance, which is why we + // sum up the glyph advances. + --i; + nfo->glyphs->advances_y[i] += yAdvance; + nfo->glyphs->advances_x[i] += xAdvance; + *nfo->numGlyphs -= 1; + } + + if (item->log_clusters) { + if (charOffset >= nextCharStop) { + nfo->glyphs->attributes[i].clusterStart = true; + currentClusterGlyph = i; + + ++nextCharStop; + for (; nextCharStop < item->length; ++nextCharStop) + if (item->charAttributes[item->from + nextCharStop].charStop) + break; + } else { + if (currentClusterGlyph == -1) + currentClusterGlyph = i; + } + item->log_clusters[charOffset] = currentClusterGlyph; + + // surrogate handling + if (charOffset < item->length - 1) { + QChar current = item->string[item->from + charOffset]; + QChar next = item->string[item->from + charOffset + 1]; + if (current.unicode() >= 0xd800 && current.unicode() < 0xdc00 + && next.unicode() >= 0xdc00 && next.unicode() < 0xe000) { + item->log_clusters[charOffset + 1] = currentClusterGlyph; + } + } + } + } + + /* + if (item) { + qDebug() << "resulting logclusters:"; + for (int i = 0; i < item->length; ++i) + qDebug() << "logClusters[" << i << "] =" << item->log_clusters[i]; + qDebug() << "clusterstarts:"; + for (int i = 0; i < *nfo->numGlyphs; ++i) + qDebug() << "clusterStart[" << i << "] =" << nfo->glyphs[i].attributes.clusterStart; + } + */ + + ATSUDirectReleaseLayoutDataArrayPtr(lineRef, kATSUDirectDataBaselineDeltaFixedArray, + (void **) &baselineDeltas); + + ATSUDirectReleaseLayoutDataArrayPtr(lineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, + (void **) &layoutData); + + *callbackStatus = kATSULayoutOperationCallbackStatusHandled; + return noErr; +} + +int QFontEngineMacMulti::fontIndexForFontID(ATSUFontID id) const +{ + for (int i = 0; i < engines.count(); ++i) { + if (engineAt(i)->fontID == id) + return i; + } + + QFontEngineMacMulti *that = const_cast(this); + QFontEngineMac *fe = new QFontEngineMac(style, id, fontDef, that); + fe->ref.ref(); + that->engines.append(fe); + return engines.count() - 1; +} + +bool QFontEngineMacMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + return stringToCMap(str, len, glyphs, nglyphs, flags, /*logClusters=*/0, /*charAttributes=*/0); +} + +bool QFontEngineMacMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags, + unsigned short *logClusters, const HB_CharAttributes *charAttributes) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + + ShaperItem shaperItem; + shaperItem.string = str; + shaperItem.from = 0; + shaperItem.length = len; + shaperItem.glyphs = *glyphs; + shaperItem.glyphs.numGlyphs = *nglyphs; + shaperItem.flags = flags; + shaperItem.log_clusters = logClusters; + shaperItem.charAttributes = charAttributes; + + const int maxChars = qMax(1, + int(SHRT_MAX / maxCharWidth()) + - 10 // subtract a few to be on the safe side + ); + if (len < maxChars || !charAttributes) + return stringToCMapInternal(str, len, glyphs, nglyphs, flags, &shaperItem); + + int charIdx = 0; + int glyphIdx = 0; + ShaperItem tmpItem = shaperItem; + + do { + tmpItem.from = shaperItem.from + charIdx; + + int charCount = qMin(maxChars, len - charIdx); + + int lastWhitespace = tmpItem.from + charCount - 1; + int lastSoftBreak = lastWhitespace; + int lastCharStop = lastSoftBreak; + for (int i = lastCharStop; i >= tmpItem.from; --i) { + if (tmpItem.charAttributes[i].whiteSpace) { + lastWhitespace = i; + break; + } if (tmpItem.charAttributes[i].lineBreakType != HB_NoBreak) { + lastSoftBreak = i; + } if (tmpItem.charAttributes[i].charStop) { + lastCharStop = i; + } + } + charCount = qMin(lastWhitespace, qMin(lastSoftBreak, lastCharStop)) - tmpItem.from + 1; + + int glyphCount = shaperItem.glyphs.numGlyphs - glyphIdx; + if (glyphCount <= 0) + return false; + tmpItem.length = charCount; + tmpItem.glyphs = shaperItem.glyphs.mid(glyphIdx, glyphCount); + tmpItem.log_clusters = shaperItem.log_clusters + charIdx; + if (!stringToCMapInternal(tmpItem.string + tmpItem.from, tmpItem.length, + &tmpItem.glyphs, &glyphCount, flags, + &tmpItem)) { + *nglyphs = glyphIdx + glyphCount; + return false; + } + for (int i = 0; i < charCount; ++i) + tmpItem.log_clusters[i] += glyphIdx; + glyphIdx += glyphCount; + charIdx += charCount; + } while (charIdx < len); + *nglyphs = glyphIdx; + glyphs->numGlyphs = glyphIdx; + + return true; +} + +bool QFontEngineMacMulti::stringToCMapInternal(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags,ShaperItem *shaperItem) const +{ + //qDebug() << "stringToCMap" << QString(str, len); + + OSStatus e = noErr; + + e = ATSUSetTextPointerLocation(textLayout, (UniChar *)(str), 0, len, len); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUSetTextPointerLocation %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + QGlyphLayoutInfo nfo; + nfo.glyphs = glyphs; + nfo.numGlyphs = nglyphs; + nfo.callbackCalled = false; + nfo.flags = flags; + nfo.shaperItem = shaperItem; + nfo.styleStrategy = fontDef.styleStrategy; + + int prevNumGlyphs = *nglyphs; + + QVarLengthArray mappedFonts(len); + for (int i = 0; i < len; ++i) + mappedFonts[i] = 0; + nfo.mappedFonts = mappedFonts.data(); + + Q_ASSERT(sizeof(void *) <= sizeof(URefCon)); + e = ATSUSetTextLayoutRefCon(textLayout, (URefCon)&nfo); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUSetTextLayoutRefCon %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + { + const int maxAttributeCount = 3; + ATSUAttributeTag tags[maxAttributeCount + 1]; + ByteCount sizes[maxAttributeCount + 1]; + ATSUAttributeValuePtr values[maxAttributeCount + 1]; + int attributeCount = 0; + + tags[attributeCount] = kATSULineLayoutOptionsTag; + ATSLineLayoutOptions layopts = kATSLineHasNoOpticalAlignment + | kATSLineIgnoreFontLeading + | kATSLineNoSpecialJustification // we do kashidas ourselves + | kATSLineDisableAllJustification + ; + + if (fontDef.styleStrategy & QFont::NoAntialias) + layopts |= kATSLineNoAntiAliasing; + + if (!kerning) + layopts |= kATSLineDisableAllKerningAdjustments; + + values[attributeCount] = &layopts; + sizes[attributeCount] = sizeof(layopts); + ++attributeCount; + + tags[attributeCount] = kATSULayoutOperationOverrideTag; + ATSULayoutOperationOverrideSpecifier spec; + spec.operationSelector = kATSULayoutOperationPostLayoutAdjustment; + spec.overrideUPP = atsuPostLayoutCallback; + values[attributeCount] = &spec; + sizes[attributeCount] = sizeof(spec); + ++attributeCount; + + // CTWritingDirection + Boolean direction; + if (flags & QTextEngine::RightToLeft) + direction = kATSURightToLeftBaseDirection; + else + direction = kATSULeftToRightBaseDirection; + tags[attributeCount] = kATSULineDirectionTag; + values[attributeCount] = &direction; + sizes[attributeCount] = sizeof(direction); + ++attributeCount; + + Q_ASSERT(attributeCount < maxAttributeCount + 1); + e = ATSUSetLayoutControls(textLayout, attributeCount, tags, sizes, values); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUSetLayoutControls %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + } + + e = ATSUSetRunStyle(textLayout, style, 0, len); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUSetRunStyle %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + if (!(fontDef.styleStrategy & QFont::NoFontMerging)) { + int pos = 0; + do { + ATSUFontID substFont = 0; + UniCharArrayOffset changedOffset = 0; + UniCharCount changeCount = 0; + + e = ATSUMatchFontsToText(textLayout, pos, len - pos, + &substFont, &changedOffset, + &changeCount); + if (e == kATSUFontsMatched) { + int fontIdx = fontIndexForFontID(substFont); + for (uint i = 0; i < changeCount; ++i) + mappedFonts[changedOffset + i] = fontIdx; + pos = changedOffset + changeCount; + ATSUSetRunStyle(textLayout, engineAt(fontIdx)->style, changedOffset, changeCount); + } else if (e == kATSUFontsNotMatched) { + pos = changedOffset + changeCount; + } + } while (pos < len && e != noErr); + } + { // trigger the a layout + // CFAttributedStringCreate, CTFramesetterCreateWithAttributedString (or perhaps Typesetter) + Rect rect; + e = ATSUMeasureTextImage(textLayout, kATSUFromTextBeginning, kATSUToTextEnd, + /*iLocationX =*/ 0, /*iLocationY =*/ 0, + &rect); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUMeasureTextImage %s: %d", long(e), __FILE__, __LINE__); + return false; + } + } + + if (!nfo.callbackCalled) { + qWarning("Qt: internal: %ld: Error ATSUMeasureTextImage did not trigger callback %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + ATSUClearLayoutCache(textLayout, kATSUFromTextBeginning); + if (prevNumGlyphs < *nfo.numGlyphs) + return false; + return true; +} + +void QFontEngineMacMulti::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + Q_ASSERT(false); + Q_UNUSED(glyphs); + Q_UNUSED(flags); +} + +void QFontEngineMacMulti::doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const +{ + //Q_ASSERT(false); +} + +void QFontEngineMacMulti::loadEngine(int /*at*/) +{ + // should never be called! + Q_ASSERT(false); +} + +bool QFontEngineMacMulti::canRender(const QChar *string, int len) +{ + ATSUSetTextPointerLocation(textLayout, reinterpret_cast(string), 0, len, len); + ATSUSetRunStyle(textLayout, style, 0, len); + + OSStatus e = noErr; + int pos = 0; + do { + FMFont substFont = 0; + UniCharArrayOffset changedOffset = 0; + UniCharCount changeCount = 0; + + // CTFontCreateForString + e = ATSUMatchFontsToText(textLayout, pos, len - pos, + &substFont, &changedOffset, + &changeCount); + if (e == kATSUFontsMatched) { + pos = changedOffset + changeCount; + } else if (e == kATSUFontsNotMatched) { + break; + } + } while (pos < len && e != noErr); + + return e == noErr || e == kATSUFontsMatched; +} + +QFontEngineMac::QFontEngineMac(ATSUStyle baseStyle, ATSUFontID fontID, const QFontDef &def, QFontEngineMacMulti *multiEngine) + : fontID(fontID), multiEngine(multiEngine), cmap(0), symbolCMap(false) +{ + fontDef = def; + ATSUCreateAndCopyStyle(baseStyle, &style); + ATSFontRef atsFont = FMGetATSFontRefFromFont(fontID); + cgFont = CGFontCreateWithPlatformFont(&atsFont); + + const int maxAttributeCount = 4; + ATSUAttributeTag tags[maxAttributeCount + 1]; + ByteCount sizes[maxAttributeCount + 1]; + ATSUAttributeValuePtr values[maxAttributeCount + 1]; + int attributeCount = 0; + + synthesisFlags = 0; + + // synthesizing using CG is not recommended + quint16 macStyle = 0; + { + uchar data[4]; + ByteCount len = 4; + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 44, 4, &data, &len) == noErr) + macStyle = qFromBigEndian(data); + } + + Boolean atsuBold = false; + Boolean atsuItalic = false; + if (fontDef.weight >= QFont::Bold) { + if (!(macStyle & 1)) { + synthesisFlags |= SynthesizedBold; + atsuBold = true; + tags[attributeCount] = kATSUQDBoldfaceTag; + sizes[attributeCount] = sizeof(atsuBold); + values[attributeCount] = &atsuBold; + ++attributeCount; + } + } + if (fontDef.style != QFont::StyleNormal) { + if (!(macStyle & 2)) { + synthesisFlags |= SynthesizedItalic; + atsuItalic = true; + tags[attributeCount] = kATSUQDItalicTag; + sizes[attributeCount] = sizeof(atsuItalic); + values[attributeCount] = &atsuItalic; + ++attributeCount; + } + } + + tags[attributeCount] = kATSUFontTag; + values[attributeCount] = &fontID; + sizes[attributeCount] = sizeof(fontID); + ++attributeCount; + + Q_ASSERT(attributeCount < maxAttributeCount + 1); + OSStatus err = ATSUSetAttributes(style, attributeCount, tags, sizes, values); + Q_ASSERT(err == noErr); + Q_UNUSED(err); + + // CTFontCopyTable + quint16 tmpFsType; + if (ATSFontGetTable(atsFont, MAKE_TAG('O', 'S', '/', '2'), 8, 2, &tmpFsType, 0) == noErr) + fsType = qFromBigEndian(tmpFsType); + else + fsType = 0; + + if (multiEngine) + transform = multiEngine->transform; + else + transform = CGAffineTransformIdentity; + + ATSUTextMeasurement metric; + + ATSUGetAttribute(style, kATSUAscentTag, sizeof(metric), &metric, 0); + m_ascent = FixRound(metric); + + ATSUGetAttribute(style, kATSUDescentTag, sizeof(metric), &metric, 0); + m_descent = FixRound(metric); + + ATSUGetAttribute(style, kATSULeadingTag, sizeof(metric), &metric, 0); + m_leading = FixRound(metric); + + ATSFontMetrics metrics; + + ATSFontGetHorizontalMetrics(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &metrics); + m_maxCharWidth = metrics.maxAdvanceWidth * fontDef.pointSize; + + ATSFontGetHorizontalMetrics(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &metrics); + m_xHeight = QFixed::fromReal(metrics.xHeight * fontDef.pointSize); + + ATSFontGetHorizontalMetrics(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &metrics); + m_averageCharWidth = QFixed::fromReal(metrics.avgAdvanceWidth * fontDef.pointSize); + + // Use width of 'X' if ATSFontGetHorizontalMetrics returns 0 for avgAdvanceWidth. + if (m_averageCharWidth == QFixed(0)) { + QChar c('X'); + QGlyphLayoutArray<1> glyphs; + int nglyphs = 1; + stringToCMap(&c, 1, &glyphs, &nglyphs, 0); + glyph_metrics_t metrics = boundingBox(glyphs); + m_averageCharWidth = metrics.width; + } +} + +QFontEngineMac::~QFontEngineMac() +{ + ATSUDisposeStyle(style); +} + +static inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +// Not used directly for shaping, only used to calculate m_averageCharWidth +bool QFontEngineMac::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (!cmap) { + cmapTable = getSfntTable(MAKE_TAG('c', 'm', 'a', 'p')); + int size = 0; + cmap = getCMap(reinterpret_cast(cmapTable.constData()), cmapTable.size(), &symbolCMap, &size); + if (!cmap) + return false; + } + if (symbolCMap) { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc); + if(!glyphs->glyphs[i] && uc < 0x100) + glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + } + } else { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc); + } + } + + *nglyphs = len; + glyphs->numGlyphs = *nglyphs; + + if (!(flags & QTextEngine::GlyphIndicesOnly)) + recalcAdvances(glyphs, flags); + + return true; +} + +void QFontEngineMac::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + Q_UNUSED(flags) + + QVarLengthArray atsuGlyphs(glyphs->numGlyphs); + for (int i = 0; i < glyphs->numGlyphs; ++i) + atsuGlyphs[i] = glyphs->glyphs[i]; + + QVarLengthArray metrics(glyphs->numGlyphs); + + ATSUGlyphGetScreenMetrics(style, glyphs->numGlyphs, atsuGlyphs.data(), sizeof(GlyphID), + /* iForcingAntiAlias =*/ false, + /* iAntiAliasSwitch =*/true, + metrics.data()); + + for (int i = 0; i < glyphs->numGlyphs; ++i) { + glyphs->advances_x[i] = QFixed::fromReal(metrics[i].deviceAdvance.x); + glyphs->advances_y[i] = QFixed::fromReal(metrics[i].deviceAdvance.y); + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + glyphs->advances_x[i] = glyphs->advances_x[i].round(); + glyphs->advances_y[i] = glyphs->advances_y[i].round(); + } + } +} + +glyph_metrics_t QFontEngineMac::boundingBox(const QGlyphLayout &glyphs) +{ + QFixed w; + bool round = fontDef.styleStrategy & QFont::ForceIntegerMetrics; + for (int i = 0; i < glyphs.numGlyphs; ++i) { + w += round ? glyphs.effectiveAdvance(i).round() + : glyphs.effectiveAdvance(i); + } + return glyph_metrics_t(0, -(ascent()), w - lastRightBearing(glyphs, round), ascent()+descent(), w, 0); +} + +glyph_metrics_t QFontEngineMac::boundingBox(glyph_t glyph) +{ + GlyphID atsuGlyph = glyph; + + ATSGlyphScreenMetrics metrics; + + ATSUGlyphGetScreenMetrics(style, 1, &atsuGlyph, 0, + /* iForcingAntiAlias =*/ false, + /* iAntiAliasSwitch =*/true, + &metrics); + + // ### check again + + glyph_metrics_t gm; + gm.width = int(metrics.width); + gm.height = int(metrics.height); + gm.x = QFixed::fromReal(metrics.topLeft.x); + gm.y = -QFixed::fromReal(metrics.topLeft.y); + gm.xoff = QFixed::fromReal(metrics.deviceAdvance.x); + gm.yoff = QFixed::fromReal(metrics.deviceAdvance.y); + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + gm.x = gm.x.floor(); + gm.y = gm.y.floor(); + gm.xoff = gm.xoff.round(); + gm.yoff = gm.yoff.round(); + } + + return gm; +} + +QFixed QFontEngineMac::ascent() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_ascent.round() + : m_ascent; +} + +QFixed QFontEngineMac::descent() const +{ + // subtract a pixel to even out the historical +1 in QFontMetrics::height(). + // Fix in Qt 5. + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_descent.round() - 1 + : m_descent; +} + +QFixed QFontEngineMac::leading() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_leading.round() + : m_leading; +} + +qreal QFontEngineMac::maxCharWidth() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? qRound(m_maxCharWidth) + : m_maxCharWidth; +} + +QFixed QFontEngineMac::xHeight() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_xHeight.round() + : m_xHeight; +} + +QFixed QFontEngineMac::averageCharWidth() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_averageCharWidth.round() + : m_averageCharWidth; +} + +static void addGlyphsToPathHelper(ATSUStyle style, glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, QPainterPath *path) +{ + if (!numGlyphs) + return; + + OSStatus e; + + QMacFontPath fontpath(0, 0, path); + ATSCubicMoveToUPP moveTo = NewATSCubicMoveToUPP(QMacFontPath::moveTo); + ATSCubicLineToUPP lineTo = NewATSCubicLineToUPP(QMacFontPath::lineTo); + ATSCubicCurveToUPP cubicTo = NewATSCubicCurveToUPP(QMacFontPath::cubicTo); + ATSCubicClosePathUPP closePath = NewATSCubicClosePathUPP(QMacFontPath::closePath); + + // CTFontCreatePathForGlyph + for (int i = 0; i < numGlyphs; ++i) { + GlyphID glyph = glyphs[i]; + + fontpath.setPosition(positions[i].x.toReal(), positions[i].y.toReal()); + ATSUGlyphGetCubicPaths(style, glyph, moveTo, lineTo, + cubicTo, closePath, &fontpath, &e); + } + + DisposeATSCubicMoveToUPP(moveTo); + DisposeATSCubicLineToUPP(lineTo); + DisposeATSCubicCurveToUPP(cubicTo); + DisposeATSCubicClosePathUPP(closePath); +} + +void QFontEngineMac::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, QPainterPath *path, + QTextItem::RenderFlags) +{ + addGlyphsToPathHelper(style, glyphs, positions, numGlyphs, path); +} + + +/*! + Helper function for alphaMapForGlyph and alphaRGBMapForGlyph. The two are identical, except for + the subpixel antialiasing... +*/ +QImage QFontEngineMac::imageForGlyph(glyph_t glyph, int margin, bool colorful) +{ + const glyph_metrics_t br = boundingBox(glyph); + QImage im(qRound(br.width)+2, qRound(br.height)+4, QImage::Format_RGB32); + im.fill(0xff000000); + + CGColorSpaceRef colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace(); + uint cgflags = kCGImageAlphaNoneSkipFirst; +#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version + cgflags |= kCGBitmapByteOrder32Host; +#endif + CGContextRef ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(), + 8, im.bytesPerLine(), colorspace, + cgflags); + CGContextSetFontSize(ctx, fontDef.pixelSize); + CGContextSetShouldAntialias(ctx, fontDef.pointSize > qt_antialiasing_threshold && !(fontDef.styleStrategy & QFont::NoAntialias)); + // turn off sub-pixel hinting - no support for that in OpenGL + CGContextSetShouldSmoothFonts(ctx, colorful); + CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); + CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, 1, 0, 0); + CGAffineTransformConcat(cgMatrix, oldTextMatrix); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, tanf(14 * acosf(0) / 90), 1, 0, 0)); + + cgMatrix = CGAffineTransformConcat(cgMatrix, transform); + + CGContextSetTextMatrix(ctx, cgMatrix); + CGContextSetRGBFillColor(ctx, 1, 1, 1, 1); + CGContextSetTextDrawingMode(ctx, kCGTextFill); + CGContextSetFont(ctx, cgFont); + + qreal pos_x = -br.x.toReal() + 1; + qreal pos_y = im.height() + br.y.toReal() - 2; + CGContextSetTextPosition(ctx, pos_x, pos_y); + + CGSize advance; + advance.width = 0; + advance.height = 0; + CGGlyph cgGlyph = glyph; + CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); + + if (synthesisFlags & QFontEngine::SynthesizedBold) { + CGContextSetTextPosition(ctx, pos_x + 0.5 * lineThickness().toReal(), pos_y); + CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); + } + + CGContextRelease(ctx); + + return im; +} + +QImage QFontEngineMac::alphaMapForGlyph(glyph_t glyph) +{ + QImage im = imageForGlyph(glyph, 2, false); + + QImage indexed(im.width(), im.height(), QImage::Format_Indexed8); + QVector colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; y= QTransform::TxScale) { + im = im.transformed(t); + } + + qGamma_correct_back_to_linear_cs(&im); + + return im; +} + + +bool QFontEngineMac::canRender(const QChar *string, int len) +{ + Q_ASSERT(false); + Q_UNUSED(string); + Q_UNUSED(len); + return false; +} + +void QFontEngineMac::draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight) +{ + QVarLengthArray positions; + QVarLengthArray glyphs; + QTransform matrix; + matrix.translate(x, y); + getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + CGContextSetFontSize(ctx, fontDef.pixelSize); + + CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); + + CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, -1, 0, -paintDeviceHeight); + + CGAffineTransformConcat(cgMatrix, oldTextMatrix); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -tanf(14 * acosf(0) / 90), 1, 0, 0)); + + cgMatrix = CGAffineTransformConcat(cgMatrix, transform); + + CGContextSetTextMatrix(ctx, cgMatrix); + + CGContextSetTextDrawingMode(ctx, kCGTextFill); + + + QVarLengthArray advances(glyphs.size()); + QVarLengthArray cgGlyphs(glyphs.size()); + + for (int i = 0; i < glyphs.size() - 1; ++i) { + advances[i].width = (positions[i + 1].x - positions[i].x).toReal(); + advances[i].height = (positions[i + 1].y - positions[i].y).toReal(); + cgGlyphs[i] = glyphs[i]; + } + advances[glyphs.size() - 1].width = 0; + advances[glyphs.size() - 1].height = 0; + cgGlyphs[glyphs.size() - 1] = glyphs[glyphs.size() - 1]; + + CGContextSetFont(ctx, cgFont); + + CGContextSetTextPosition(ctx, positions[0].x.toReal(), positions[0].y.toReal()); + + CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); + + if (synthesisFlags & QFontEngine::SynthesizedBold) { + CGContextSetTextPosition(ctx, positions[0].x.toReal() + 0.5 * lineThickness().toReal(), + positions[0].y.toReal()); + + CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); + } + + CGContextSetTextMatrix(ctx, oldTextMatrix); +} + +QFontEngine::FaceId QFontEngineMac::faceId() const +{ + FaceId ret; +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) +if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + // CTFontGetPlatformFont + FSRef ref; + if (ATSFontGetFileReference(FMGetATSFontRefFromFont(fontID), &ref) != noErr) + return ret; + ret.filename = QByteArray(128, 0); + ret.index = fontID; + FSRefMakePath(&ref, (UInt8 *)ret.filename.data(), ret.filename.size()); +}else +#endif +{ + FSSpec spec; + if (ATSFontGetFileSpecification(FMGetATSFontRefFromFont(fontID), &spec) != noErr) + return ret; + + FSRef ref; + FSpMakeFSRef(&spec, &ref); + ret.filename = QByteArray(128, 0); + ret.index = fontID; + FSRefMakePath(&ref, (UInt8 *)ret.filename.data(), ret.filename.size()); +} + return ret; +} + +QByteArray QFontEngineMac::getSfntTable(uint tag) const +{ + ATSFontRef atsFont = FMGetATSFontRefFromFont(fontID); + + ByteCount length; + OSStatus status = ATSFontGetTable(atsFont, tag, 0, 0, 0, &length); + if (status != noErr) + return QByteArray(); + QByteArray table(length, 0); + // CTFontCopyTable + status = ATSFontGetTable(atsFont, tag, 0, table.length(), table.data(), &length); + if (status != noErr) + return QByteArray(); + return table; +} + +QFontEngine::Properties QFontEngineMac::properties() const +{ + QFontEngine::Properties props; + ATSFontRef atsFont = FMGetATSFontRefFromFont(fontID); + quint16 tmp; + // CTFontGetUnitsPerEm + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 18, 2, &tmp, 0) == noErr) + props.emSquare = qFromBigEndian(tmp); + struct { + qint16 xMin; + qint16 yMin; + qint16 xMax; + qint16 yMax; + } bbox; + bbox.xMin = bbox.xMax = bbox.yMin = bbox.yMax = 0; + // CTFontGetBoundingBox + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 36, 8, &bbox, 0) == noErr) { + bbox.xMin = qFromBigEndian(bbox.xMin); + bbox.yMin = qFromBigEndian(bbox.yMin); + bbox.xMax = qFromBigEndian(bbox.xMax); + bbox.yMax = qFromBigEndian(bbox.yMax); + } + struct { + qint16 ascender; + qint16 descender; + qint16 linegap; + } metrics; + metrics.ascender = metrics.descender = metrics.linegap = 0; + // CTFontGetAscent, etc. + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'h', 'e', 'a'), 4, 6, &metrics, 0) == noErr) { + metrics.ascender = qFromBigEndian(metrics.ascender); + metrics.descender = qFromBigEndian(metrics.descender); + metrics.linegap = qFromBigEndian(metrics.linegap); + } + props.ascent = metrics.ascender; + props.descent = -metrics.descender; + props.leading = metrics.linegap; + props.boundingBox = QRectF(bbox.xMin, -bbox.yMax, + bbox.xMax - bbox.xMin, + bbox.yMax - bbox.yMin); + props.italicAngle = 0; + props.capHeight = props.ascent; + + qint16 lw = 0; + // fonts lie + if (ATSFontGetTable(atsFont, MAKE_TAG('p', 'o', 's', 't'), 10, 2, &lw, 0) == noErr) + lw = qFromBigEndian(lw); + props.lineWidth = lw; + + // CTFontCopyPostScriptName + QCFString psName; + if (ATSFontGetPostScriptName(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &psName) == noErr) + props.postscriptName = QString(psName).toUtf8(); + props.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(props.postscriptName); + return props; +} + +void QFontEngineMac::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) +{ + ATSUStyle unscaledStyle; + ATSUCreateAndCopyStyle(style, &unscaledStyle); + + int emSquare = properties().emSquare.toInt(); + + const int maxAttributeCount = 4; + ATSUAttributeTag tags[maxAttributeCount + 1]; + ByteCount sizes[maxAttributeCount + 1]; + ATSUAttributeValuePtr values[maxAttributeCount + 1]; + int attributeCount = 0; + + Fixed size = FixRatio(emSquare, 1); + tags[attributeCount] = kATSUSizeTag; + sizes[attributeCount] = sizeof(size); + values[attributeCount] = &size; + ++attributeCount; + + Q_ASSERT(attributeCount < maxAttributeCount + 1); + OSStatus err = ATSUSetAttributes(unscaledStyle, attributeCount, tags, sizes, values); + Q_ASSERT(err == noErr); + Q_UNUSED(err); + + // various CTFont metrics functions: CTFontGetBoundingRectsForGlyphs, CTFontGetAdvancesForGlyphs + GlyphID atsuGlyph = glyph; + ATSGlyphScreenMetrics atsuMetrics; + ATSUGlyphGetScreenMetrics(unscaledStyle, 1, &atsuGlyph, 0, + /* iForcingAntiAlias =*/ false, + /* iAntiAliasSwitch =*/true, + &atsuMetrics); + + metrics->width = int(atsuMetrics.width); + metrics->height = int(atsuMetrics.height); + metrics->x = QFixed::fromReal(atsuMetrics.topLeft.x); + metrics->y = -QFixed::fromReal(atsuMetrics.topLeft.y); + metrics->xoff = QFixed::fromReal(atsuMetrics.deviceAdvance.x); + metrics->yoff = QFixed::fromReal(atsuMetrics.deviceAdvance.y); + + QFixedPoint p; + addGlyphsToPathHelper(unscaledStyle, &glyph, &p, 1, path); + + ATSUDisposeStyle(unscaledStyle); +} +#endif // !QT_MAC_USE_COCOA + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_mac_p.h b/src/gui/text/qfontengine_mac_p.h new file mode 100644 index 0000000000..385fa83fe9 --- /dev/null +++ b/src/gui/text/qfontengine_mac_p.h @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_MAC_P_H +#define QFONTENGINE_MAC_P_H + +#include + +#ifndef QT_MAC_USE_COCOA +class QFontEngineMacMulti; +class QFontEngineMac : public QFontEngine +{ + friend class QFontEngineMacMulti; +public: + QFontEngineMac(ATSUStyle baseStyle, ATSUFontID fontID, const QFontDef &def, QFontEngineMacMulti *multiEngine = 0); + virtual ~QFontEngineMac(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *numGlyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual qreal maxCharWidth() const; + virtual QFixed averageCharWidth() const; + + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, + QPainterPath *path, QTextItem::RenderFlags); + + virtual const char *name() const { return "QFontEngineMac"; } + + virtual bool canRender(const QChar *string, int len); + + virtual int synthesized() const { return synthesisFlags; } + + virtual Type type() const { return QFontEngine::Mac; } + + void draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight); + + virtual FaceId faceId() const; + virtual QByteArray getSfntTable(uint tag) const; + virtual Properties properties() const; + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + virtual QImage alphaMapForGlyph(glyph_t); + virtual QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, int margin, const QTransform &t); + +private: + QImage imageForGlyph(glyph_t glyph, int margin, bool colorful); + + ATSUFontID fontID; + QCFType cgFont; + ATSUStyle style; + int synthesisFlags; + mutable QGlyphLayout kashidaGlyph; + QFontEngineMacMulti *multiEngine; + mutable const unsigned char *cmap; + mutable bool symbolCMap; + mutable QByteArray cmapTable; + CGAffineTransform transform; + QFixed m_ascent; + QFixed m_descent; + QFixed m_leading; + qreal m_maxCharWidth; + QFixed m_xHeight; + QFixed m_averageCharWidth; +}; + +class QFontEngineMacMulti : public QFontEngineMulti +{ + friend class QFontEngineMac; +public: + // internal + struct ShaperItem + { + inline ShaperItem() : string(0), from(0), length(0), + log_clusters(0), charAttributes(0) {} + + const QChar *string; + int from; + int length; + QGlyphLayout glyphs; + unsigned short *log_clusters; + const HB_CharAttributes *charAttributes; + QTextEngine::ShaperFlags flags; + }; + + QFontEngineMacMulti(const ATSFontFamilyRef &atsFamily, const ATSFontRef &atsFontRef, const QFontDef &fontDef, bool kerning); + virtual ~QFontEngineMacMulti(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags, + unsigned short *logClusters, const HB_CharAttributes *charAttributes) const; + + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + virtual void doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + virtual const char *name() const { return "ATSUI"; } + + virtual bool canRender(const QChar *string, int len); + + inline ATSUFontID macFontID() const { return fontID; } + +protected: + virtual void loadEngine(int at); + +private: + inline const QFontEngineMac *engineAt(int i) const + { return static_cast(engines.at(i)); } + + bool stringToCMapInternal(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags, ShaperItem *item) const; + + int fontIndexForFontID(ATSUFontID id) const; + + ATSUFontID fontID; + uint kerning : 1; + + mutable ATSUTextLayout textLayout; + mutable ATSUStyle style; + CGAffineTransform transform; +}; +#endif //!QT_MAC_USE_COCOA + +#endif // QFONTENGINE_MAC_P_H diff --git a/src/gui/text/qfontengine_p.h b/src/gui/text/qfontengine_p.h new file mode 100644 index 0000000000..5b39fd39ad --- /dev/null +++ b/src/gui/text/qfontengine_p.h @@ -0,0 +1,452 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_P_H +#define QFONTENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qglobal.h" +#include "QtCore/qatomic.h" +#include +#include +#include "private/qtextengine_p.h" +#include "private/qfont_p.h" + +#ifdef Q_WS_WIN +# include "QtCore/qt_windows.h" +#endif + +#ifdef Q_WS_MAC +# include "private/qt_mac_p.h" +# include "QtCore/qmap.h" +# include "QtCore/qcache.h" +# include "private/qcore_mac_p.h" +#endif + +#include + +struct glyph_metrics_t; +typedef unsigned int glyph_t; + +QT_BEGIN_NAMESPACE + +class QChar; +class QPainterPath; + +class QTextEngine; +struct QGlyphLayout; + +#define MAKE_TAG(ch1, ch2, ch3, ch4) (\ + (((quint32)(ch1)) << 24) | \ + (((quint32)(ch2)) << 16) | \ + (((quint32)(ch3)) << 8) | \ + ((quint32)(ch4)) \ + ) + + +class Q_GUI_EXPORT QFontEngine : public QObject +{ +public: + enum Type { + Box, + Multi, + + // X11 types + XLFD, + + // MS Windows types + Win, + + // Apple Mac OS types + Mac, + + // QWS types + Freetype, + QPF1, + QPF2, + Proxy, + + // S60 types + S60FontEngine, // Cannot be simply called "S60". Reason is qt_s60Data.h + + DirectWrite, + + TestFontEngine = 0x1000 + }; + + enum GlyphFormat { + Format_None, + Format_Render = Format_None, + Format_Mono, + Format_A8, + Format_A32 + }; + + QFontEngine(); + virtual ~QFontEngine(); + + // all of these are in unscaled metrics if the engine supports uncsaled metrics, + // otherwise in design metrics + struct Properties { + QByteArray postscriptName; + QByteArray copyright; + QRectF boundingBox; + QFixed emSquare; + QFixed ascent; + QFixed descent; + QFixed leading; + QFixed italicAngle; + QFixed capHeight; + QFixed lineWidth; + }; + virtual Properties properties() const; + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + QByteArray getSfntTable(uint /*tag*/) const; + virtual bool getSfntTableData(uint /*tag*/, uchar * /*buffer*/, uint * /*length*/) const { return false; } + + struct FaceId { + FaceId() : index(0), encoding(0) {} + QByteArray filename; + int index; + int encoding; + }; + virtual FaceId faceId() const { return FaceId(); } + enum SynthesizedFlags { + SynthesizedItalic = 0x1, + SynthesizedBold = 0x2, + SynthesizedStretch = 0x4 + }; + virtual int synthesized() const { return 0; } + virtual bool supportsSubPixelPositions() const { return false; } + + virtual QFixed emSquareSize() const { return ascent(); } + + /* returns 0 as glyph index for non existent glyphs */ + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const = 0; + + /** + * This is a callback from harfbuzz. The font engine uses the font-system in use to find out the + * advances of each glyph and set it on the layout. + */ + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const {} + virtual void doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const; + +#if !defined(Q_WS_X11) && !defined(Q_WS_WIN) && !defined(Q_WS_MAC) && !defined(Q_OS_SYMBIAN) && !defined(Q_WS_QPA) + virtual void draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &si) = 0; +#endif + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, + QPainterPath *path, QTextItem::RenderFlags flags); + + void getGlyphPositions(const QGlyphLayout &glyphs, const QTransform &matrix, QTextItem::RenderFlags flags, + QVarLengthArray &glyphs_out, QVarLengthArray &positions); + + virtual void addOutlineToPath(qreal, qreal, const QGlyphLayout &, QPainterPath *, QTextItem::RenderFlags flags); + void addBitmapFontToPath(qreal x, qreal y, const QGlyphLayout &, QPainterPath *, QTextItem::RenderFlags); + /** + * Create a qimage with the alpha values for the glyph. + * Returns an image indexed_8 with index values ranging from 0=fully transparent to 255=opaque + */ + virtual QImage alphaMapForGlyph(glyph_t); + virtual QImage alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition); + virtual QImage alphaMapForGlyph(glyph_t, const QTransform &t); + virtual QImage alphaMapForGlyph(glyph_t, QFixed subPixelPosition, const QTransform &t); + virtual QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, int margin, const QTransform &t); + + virtual glyph_metrics_t alphaMapBoundingBox(glyph_t glyph, QFixed /*subPixelPosition*/, const QTransform &matrix, GlyphFormat /*format*/) + { + return boundingBox(glyph, matrix); + } + + virtual void removeGlyphFromCache(glyph_t); + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs) = 0; + virtual glyph_metrics_t boundingBox(glyph_t glyph) = 0; + virtual glyph_metrics_t boundingBox(glyph_t glyph, const QTransform &matrix); + glyph_metrics_t tightBoundingBox(const QGlyphLayout &glyphs); + + virtual QFixed ascent() const = 0; + virtual QFixed descent() const = 0; + virtual QFixed leading() const = 0; + virtual QFixed xHeight() const; + virtual QFixed averageCharWidth() const; + + virtual QFixed lineThickness() const; + virtual QFixed underlinePosition() const; + + virtual qreal maxCharWidth() const = 0; + virtual qreal minLeftBearing() const { return qreal(); } + virtual qreal minRightBearing() const { return qreal(); } + + virtual void getGlyphBearings(glyph_t glyph, qreal *leftBearing = 0, qreal *rightBearing = 0); + + virtual const char *name() const = 0; + + virtual bool canRender(const QChar *string, int len) = 0; + + virtual Type type() const = 0; + + virtual int glyphCount() const; + + HB_Font harfbuzzFont() const; + HB_Face harfbuzzFace() const; + + virtual HB_Error getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints); + + void setGlyphCache(void *key, QFontEngineGlyphCache *data); + QFontEngineGlyphCache *glyphCache(void *key, QFontEngineGlyphCache::Type type, const QTransform &transform) const; + + static const uchar *getCMap(const uchar *table, uint tableSize, bool *isSymbolFont, int *cmapSize); + static quint32 getTrueTypeGlyphIndex(const uchar *cmap, uint unicode); + + static QByteArray convertToPostscriptFontFamilyName(const QByteArray &fontFamily); + + QAtomicInt ref; + QFontDef fontDef; + uint cache_cost; // amount of mem used in kb by the font + int cache_count; + uint fsType : 16; + bool symbol; + mutable HB_FontRec hbFont; + mutable HB_Face hbFace; +#if defined(Q_WS_WIN) || defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_WS_QPA) || defined(Q_OS_SYMBIAN) + struct KernPair { + uint left_right; + QFixed adjust; + + inline bool operator<(const KernPair &other) const + { + return left_right < other.left_right; + } + }; + QVector kerning_pairs; + void loadKerningPairs(QFixed scalingFactor); +#endif + + int glyphFormat; + +protected: + static const QVector &grayPalette(); + QFixed lastRightBearing(const QGlyphLayout &glyphs, bool round = false); + +private: + struct GlyphCacheEntry { + void *context; + QExplicitlySharedDataPointer cache; + bool operator==(const GlyphCacheEntry &other) { return context == other.context && cache == other.cache; } + }; + + mutable QLinkedList m_glyphCaches; +}; + +inline bool operator ==(const QFontEngine::FaceId &f1, const QFontEngine::FaceId &f2) +{ + return (f1.index == f2.index) && (f1.encoding == f2.encoding) && (f1.filename == f2.filename); +} + +inline uint qHash(const QFontEngine::FaceId &f) +{ + return qHash((f.index << 16) + f.encoding) + qHash(f.filename); +} + + +class QGlyph; + +#if defined(Q_WS_QWS) + +#ifndef QT_NO_QWS_QPF + +class QFontEngineQPF1Data; + +class QFontEngineQPF1 : public QFontEngine +{ +public: + QFontEngineQPF1(const QFontDef&, const QString &fn); + ~QFontEngineQPF1(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + virtual void draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &si); + virtual void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags); + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const; + virtual qreal minRightBearing() const; + virtual QFixed underlinePosition() const; + virtual QFixed lineThickness() const; + + virtual Type type() const; + + virtual bool canRender(const QChar *string, int len); + inline const char *name() const { return 0; } + virtual QImage alphaMapForGlyph(glyph_t); + + + QFontEngineQPF1Data *d; +}; +#endif // QT_NO_QWS_QPF + +#endif // QWS + + +class QFontEngineBox : public QFontEngine +{ +public: + QFontEngineBox(int size); + ~QFontEngineBox(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + +#if !defined(Q_WS_X11) && !defined(Q_WS_WIN) && !defined(Q_WS_MAC) && !defined(Q_OS_SYMBIAN) + void draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &si); +#endif + virtual void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags); + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const { return 0; } + virtual qreal minRightBearing() const { return 0; } + virtual QImage alphaMapForGlyph(glyph_t); + +#ifdef Q_WS_X11 + int cmap() const; +#endif + virtual const char *name() const; + + virtual bool canRender(const QChar *string, int len); + + virtual Type type() const; + inline int size() const { return _size; } + +private: + friend class QFontPrivate; + int _size; +}; + +class QFontEngineMulti : public QFontEngine +{ +public: + explicit QFontEngineMulti(int engineCount); + ~QFontEngineMulti(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags) const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + virtual void doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const; + virtual void addOutlineToPath(qreal, qreal, const QGlyphLayout &, QPainterPath *, QTextItem::RenderFlags flags); + virtual void getGlyphBearings(glyph_t glyph, qreal *leftBearing = 0, qreal *rightBearing = 0); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual QFixed averageCharWidth() const; + virtual QImage alphaMapForGlyph(glyph_t); + + virtual QFixed lineThickness() const; + virtual QFixed underlinePosition() const; + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const; + virtual qreal minRightBearing() const; + + virtual inline Type type() const + { return QFontEngine::Multi; } + + virtual bool canRender(const QChar *string, int len); + inline virtual const char *name() const + { return "Multi"; } + + QFontEngine *engine(int at) const + {Q_ASSERT(at < engines.size()); return engines.at(at); } + + +protected: + friend class QPSPrintEnginePrivate; + friend class QPSPrintEngineFontMulti; + friend class QRawFont; + virtual void loadEngine(int at) = 0; + QVector engines; +}; + +class QTestFontEngine : public QFontEngineBox +{ +public: + QTestFontEngine(int size) : QFontEngineBox(size) {} + virtual Type type() const { return TestFontEngine; } +}; + +QT_END_NAMESPACE + +#ifdef Q_WS_WIN +# include "private/qfontengine_win_p.h" +#endif + +#if defined(Q_OS_SYMBIAN) && !defined(QT_NO_FREETYPE) +# include "private/qfontengine_ft_p.h" +#endif + +#endif // QFONTENGINE_P_H diff --git a/src/gui/text/qfontengine_qpa.cpp b/src/gui/text/qfontengine_qpa.cpp new file mode 100644 index 0000000000..851bb5964c --- /dev/null +++ b/src/gui/text/qfontengine_qpa.cpp @@ -0,0 +1,691 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontengine_qpa_p.h" + +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +//#define DEBUG_HEADER +//#define DEBUG_FONTENGINE + +static QFontEngineQPA::TagType tagTypes[QFontEngineQPA::NumTags] = { + QFontEngineQPA::StringType, // FontName + QFontEngineQPA::StringType, // FileName + QFontEngineQPA::UInt32Type, // FileIndex + QFontEngineQPA::UInt32Type, // FontRevision + QFontEngineQPA::StringType, // FreeText + QFontEngineQPA::FixedType, // Ascent + QFontEngineQPA::FixedType, // Descent + QFontEngineQPA::FixedType, // Leading + QFontEngineQPA::FixedType, // XHeight + QFontEngineQPA::FixedType, // AverageCharWidth + QFontEngineQPA::FixedType, // MaxCharWidth + QFontEngineQPA::FixedType, // LineThickness + QFontEngineQPA::FixedType, // MinLeftBearing + QFontEngineQPA::FixedType, // MinRightBearing + QFontEngineQPA::FixedType, // UnderlinePosition + QFontEngineQPA::UInt8Type, // GlyphFormat + QFontEngineQPA::UInt8Type, // PixelSize + QFontEngineQPA::UInt8Type, // Weight + QFontEngineQPA::UInt8Type, // Style + QFontEngineQPA::StringType, // EndOfHeader + QFontEngineQPA::BitFieldType// WritingSystems +}; + + +#if defined(DEBUG_HEADER) +# define DEBUG_VERIFY qDebug +#else +# define DEBUG_VERIFY if (0) qDebug +#endif + +#define READ_VERIFY(type, variable) \ + if (tagPtr + sizeof(type) > endPtr) { \ + DEBUG_VERIFY() << "read verify failed in line" << __LINE__; \ + return 0; \ + } \ + variable = qFromBigEndian(tagPtr); \ + DEBUG_VERIFY() << "read value" << variable << "of type " #type; \ + tagPtr += sizeof(type) + +template +T readValue(const uchar *&data) +{ + T value = qFromBigEndian(data); + data += sizeof(T); + return value; +} + +#define VERIFY(condition) \ + if (!(condition)) { \ + DEBUG_VERIFY() << "condition " #condition " failed in line" << __LINE__; \ + return 0; \ + } + +#define VERIFY_TAG(condition) \ + if (!(condition)) { \ + DEBUG_VERIFY() << "verifying tag condition " #condition " failed in line" << __LINE__ << "with tag" << tag; \ + return 0; \ + } + +static inline const uchar *verifyTag(const uchar *tagPtr, const uchar *endPtr) +{ + quint16 tag, length; + READ_VERIFY(quint16, tag); + READ_VERIFY(quint16, length); + if (tag == QFontEngineQPA::Tag_EndOfHeader) + return endPtr; + if (tag < QFontEngineQPA::NumTags) { + switch (tagTypes[tag]) { + case QFontEngineQPA::BitFieldType: + case QFontEngineQPA::StringType: + // can't do anything... + break; + case QFontEngineQPA::UInt32Type: + VERIFY_TAG(length == sizeof(quint32)); + break; + case QFontEngineQPA::FixedType: + VERIFY_TAG(length == sizeof(quint32)); + break; + case QFontEngineQPA::UInt8Type: + VERIFY_TAG(length == sizeof(quint8)); + break; + } +#if defined(DEBUG_HEADER) + if (length == 1) + qDebug() << "tag data" << hex << *tagPtr; + else if (length == 4) + qDebug() << "tag data" << hex << tagPtr[0] << tagPtr[1] << tagPtr[2] << tagPtr[3]; +#endif + } + return tagPtr + length; +} + +const QFontEngineQPA::Glyph *QFontEngineQPA::findGlyph(glyph_t g) const +{ + if (!g || g >= glyphMapEntries) + return 0; + const quint32 *gmapPtr = reinterpret_cast(fontData + glyphMapOffset); + quint32 glyphPos = qFromBigEndian(gmapPtr[g]); + if (glyphPos > glyphDataSize) { + if (glyphPos == 0xffffffff) + return 0; +#if defined(DEBUG_FONTENGINE) + qDebug() << "glyph" << g << "outside of glyphData, remapping font file"; +#endif + if (glyphPos > glyphDataSize) + return 0; + } + return reinterpret_cast(fontData + glyphDataOffset + glyphPos); +} + +bool QFontEngineQPA::verifyHeader(const uchar *data, int size) +{ + VERIFY(size >= int(sizeof(Header))); + const Header *header = reinterpret_cast(data); + if (header->magic[0] != 'Q' + || header->magic[1] != 'P' + || header->magic[2] != 'F' + || header->magic[3] != '2') + return false; + + VERIFY(header->majorVersion <= CurrentMajorVersion); + const quint16 dataSize = qFromBigEndian(header->dataSize); + VERIFY(size >= int(sizeof(Header)) + dataSize); + + const uchar *tagPtr = data + sizeof(Header); + const uchar *tagEndPtr = tagPtr + dataSize; + while (tagPtr < tagEndPtr - 3) { + tagPtr = verifyTag(tagPtr, tagEndPtr); + VERIFY(tagPtr); + } + + VERIFY(tagPtr <= tagEndPtr); + return true; +} + +QVariant QFontEngineQPA::extractHeaderField(const uchar *data, HeaderTag requestedTag) +{ + const Header *header = reinterpret_cast(data); + const uchar *tagPtr = data + sizeof(Header); + const uchar *endPtr = tagPtr + qFromBigEndian(header->dataSize); + while (tagPtr < endPtr - 3) { + quint16 tag = readValue(tagPtr); + quint16 length = readValue(tagPtr); + if (tag == requestedTag) { + switch (tagTypes[requestedTag]) { + case StringType: + return QVariant(QString::fromUtf8(reinterpret_cast(tagPtr), length)); + case UInt32Type: + return QVariant(readValue(tagPtr)); + case UInt8Type: + return QVariant(uint(*tagPtr)); + case FixedType: + return QVariant(QFixed::fromFixed(readValue(tagPtr)).toReal()); + case BitFieldType: + return QVariant(QByteArray(reinterpret_cast(tagPtr), length)); + } + return QVariant(); + } else if (tag == Tag_EndOfHeader) { + break; + } + tagPtr += length; + } + + return QVariant(); +} + + + +static inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +QFontEngineQPA::QFontEngineQPA(const QFontDef &def, const QByteArray &data) + : fontData(reinterpret_cast(data.constData())), dataSize(data.size()) +{ + fontDef = def; + cache_cost = 100; + externalCMap = 0; + cmapOffset = 0; + cmapSize = 0; + glyphMapOffset = 0; + glyphMapEntries = 0; + glyphDataOffset = 0; + glyphDataSize = 0; + kerning_pairs_loaded = false; + readOnly = true; + +#if defined(DEBUG_FONTENGINE) + qDebug() << "QFontEngineQPA::QFontEngineQPA( fd =" << fd << ", renderingFontEngine =" << renderingFontEngine << ')'; +#endif + + if (!verifyHeader(fontData, dataSize)) { +#if defined(DEBUG_FONTENGINE) + qDebug() << "verifyHeader failed!"; +#endif + return; + } + + const Header *header = reinterpret_cast(fontData); + + readOnly = (header->lock == 0xffffffff); + + const uchar *imgData = fontData + sizeof(Header) + qFromBigEndian(header->dataSize); + const uchar *endPtr = fontData + dataSize; + while (imgData <= endPtr - 8) { + quint16 blockTag = readValue(imgData); + imgData += 2; // skip padding + quint32 blockSize = readValue(imgData); + + if (blockTag == CMapBlock) { + cmapOffset = imgData - fontData; + cmapSize = blockSize; + } else if (blockTag == GMapBlock) { + glyphMapOffset = imgData - fontData; + glyphMapEntries = blockSize / 4; + } else if (blockTag == GlyphBlock) { + glyphDataOffset = imgData - fontData; + glyphDataSize = blockSize; + } + + imgData += blockSize; + } + + face_id.filename = QFile::encodeName(extractHeaderField(fontData, Tag_FileName).toString()); + face_id.index = extractHeaderField(fontData, Tag_FileIndex).toInt(); + + // get the real cmap + if (cmapOffset) { + int tableSize = cmapSize; + const uchar *cmapPtr = getCMap(fontData + cmapOffset, tableSize, &symbol, &cmapSize); + if (cmapPtr) + cmapOffset = cmapPtr - fontData; + else + cmapOffset = 0; + } else if (externalCMap) { + int tableSize = cmapSize; + externalCMap = getCMap(externalCMap, tableSize, &symbol, &cmapSize); + } + + // verify all the positions in the glyphMap + if (glyphMapOffset) { + const quint32 *gmapPtr = reinterpret_cast(fontData + glyphMapOffset); + for (uint i = 0; i < glyphMapEntries; ++i) { + quint32 glyphDataPos = qFromBigEndian(gmapPtr[i]); + if (glyphDataPos == 0xffffffff) + continue; + if (glyphDataPos >= glyphDataSize) { + // error + glyphMapOffset = 0; + glyphMapEntries = 0; + break; + } + } + } + +#if defined(DEBUG_FONTENGINE) + if (!isValid()) + qDebug() << "fontData" << fontData << "dataSize" << dataSize + << "externalCMap" << externalCMap << "cmapOffset" << cmapOffset + << "glyphMapOffset" << glyphMapOffset << "glyphDataOffset" << glyphDataOffset + << "fd" << fd << "glyphDataSize" << glyphDataSize; +#endif +} + +QFontEngineQPA::~QFontEngineQPA() +{ +} + +bool QFontEngineQPA::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + Q_UNUSED(tag); + Q_UNUSED(buffer); + *length = 0; + return false; +} + +bool QFontEngineQPA::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + +#if defined(DEBUG_FONTENGINE) + QSet seenGlyphs; +#endif + + const uchar *cmap = externalCMap ? externalCMap : (fontData + cmapOffset); + + bool mirrored = flags & QTextEngine::RightToLeft; + int glyph_pos = 0; + if (symbol) { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + if (mirrored) + uc = QChar::mirroredChar(uc); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc); + if(!glyphs->glyphs[glyph_pos] && uc < 0x100) + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + ++glyph_pos; + } + } else { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + if (mirrored) + uc = QChar::mirroredChar(uc); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc); +#if 0 && defined(DEBUG_FONTENGINE) + QChar c(uc); + if (!findGlyph(glyphs[glyph_pos].glyph) && !seenGlyphs.contains(c)) + qDebug() << "glyph for character" << c << '/' << hex << uc << "is" << dec << glyphs[glyph_pos].glyph; + + seenGlyphs.insert(c); +#endif + ++glyph_pos; + } + } + + *nglyphs = glyph_pos; + glyphs->numGlyphs = glyph_pos; + recalcAdvances(glyphs, flags); + return true; +} + +void QFontEngineQPA::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const +{ + for (int i = 0; i < glyphs->numGlyphs; ++i) { + const Glyph *g = findGlyph(glyphs->glyphs[i]); + if (!g) { + glyphs->glyphs[i] = 0; + continue; + } + glyphs->advances_x[i] = g->advance; + glyphs->advances_y[i] = 0; + } +} + +QImage QFontEngineQPA::alphaMapForGlyph(glyph_t g) +{ + const Glyph *glyph = findGlyph(g); + if (!glyph) + return QImage(); + + const uchar *bits = ((const uchar *) glyph) + sizeof(Glyph); + + QImage image(bits,glyph->width, glyph->height, glyph->bytesPerLine, QImage::Format_Indexed8); + + return image; +} + +void QFontEngineQPA::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + addBitmapFontToPath(x, y, glyphs, path, flags); +} + +glyph_metrics_t QFontEngineQPA::boundingBox(const QGlyphLayout &glyphs) +{ + glyph_metrics_t overall; + // initialize with line height, we get the same behaviour on all platforms + overall.y = -ascent(); + overall.height = ascent() + descent() + 1; + + QFixed ymax = 0; + QFixed xmax = 0; + for (int i = 0; i < glyphs.numGlyphs; i++) { + const Glyph *g = findGlyph(glyphs.glyphs[i]); + if (!g) + continue; + + QFixed x = overall.xoff + glyphs.offsets[i].x + g->x; + QFixed y = overall.yoff + glyphs.offsets[i].y + g->y; + overall.x = qMin(overall.x, x); + overall.y = qMin(overall.y, y); + xmax = qMax(xmax, x + g->width); + ymax = qMax(ymax, y + g->height); + overall.xoff += g->advance; + } + overall.height = qMax(overall.height, ymax - overall.y); + overall.width = xmax - overall.x; + + return overall; +} + +glyph_metrics_t QFontEngineQPA::boundingBox(glyph_t glyph) +{ + glyph_metrics_t overall; + const Glyph *g = findGlyph(glyph); + if (!g) + return overall; + overall.x = g->x; + overall.y = g->y; + overall.width = g->width; + overall.height = g->height; + overall.xoff = g->advance; + return overall; +} + +QFixed QFontEngineQPA::ascent() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_Ascent).value()); +} + +QFixed QFontEngineQPA::descent() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_Descent).value()); +} + +QFixed QFontEngineQPA::leading() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_Leading).value()); +} + +qreal QFontEngineQPA::maxCharWidth() const +{ + return extractHeaderField(fontData, Tag_MaxCharWidth).value(); +} + +qreal QFontEngineQPA::minLeftBearing() const +{ + return extractHeaderField(fontData, Tag_MinLeftBearing).value(); +} + +qreal QFontEngineQPA::minRightBearing() const +{ + return extractHeaderField(fontData, Tag_MinRightBearing).value(); +} + +QFixed QFontEngineQPA::underlinePosition() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_UnderlinePosition).value()); +} + +QFixed QFontEngineQPA::lineThickness() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_LineThickness).value()); +} + +QFontEngine::Type QFontEngineQPA::type() const +{ + return QFontEngine::QPF2; +} + +bool QFontEngineQPA::canRender(const QChar *string, int len) +{ + const uchar *cmap = externalCMap ? externalCMap : (fontData + cmapOffset); + + if (symbol) { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(string, i, len); + glyph_t g = getTrueTypeGlyphIndex(cmap, uc); + if(!g && uc < 0x100) + g = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + if (!g) + return false; + } + } else { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(string, i, len); + if (!getTrueTypeGlyphIndex(cmap, uc)) + return false; + } + } + return true; +} + +bool QFontEngineQPA::isValid() const +{ + return fontData && dataSize && (cmapOffset || externalCMap) + && glyphMapOffset && glyphDataOffset && glyphDataSize > 0; +} + +void QPAGenerator::generate() +{ + writeHeader(); + writeGMap(); + writeBlock(QFontEngineQPA::GlyphBlock, QByteArray()); + + dev->seek(4); // position of header.lock + writeUInt32(0); +} + +void QPAGenerator::writeHeader() +{ + QFontEngineQPA::Header header; + + header.magic[0] = 'Q'; + header.magic[1] = 'P'; + header.magic[2] = 'F'; + header.magic[3] = '2'; + header.lock = 1; + header.majorVersion = QFontEngineQPA::CurrentMajorVersion; + header.minorVersion = QFontEngineQPA::CurrentMinorVersion; + header.dataSize = 0; + dev->write((const char *)&header, sizeof(header)); + + writeTaggedString(QFontEngineQPA::Tag_FontName, fe->fontDef.family.toUtf8()); + + QFontEngine::FaceId face = fe->faceId(); + writeTaggedString(QFontEngineQPA::Tag_FileName, face.filename); + writeTaggedUInt32(QFontEngineQPA::Tag_FileIndex, face.index); + + { + uchar data[4]; + uint len = 4; + bool ok = fe->getSfntTableData(MAKE_TAG('h', 'e', 'a', 'd'), data, &len); + if (ok) { + const quint32 revision = qFromBigEndian(data); + writeTaggedUInt32(QFontEngineQPA::Tag_FontRevision, revision); + } + } + + writeTaggedQFixed(QFontEngineQPA::Tag_Ascent, fe->ascent()); + writeTaggedQFixed(QFontEngineQPA::Tag_Descent, fe->descent()); + writeTaggedQFixed(QFontEngineQPA::Tag_Leading, fe->leading()); + writeTaggedQFixed(QFontEngineQPA::Tag_XHeight, fe->xHeight()); + writeTaggedQFixed(QFontEngineQPA::Tag_AverageCharWidth, fe->averageCharWidth()); + writeTaggedQFixed(QFontEngineQPA::Tag_MaxCharWidth, QFixed::fromReal(fe->maxCharWidth())); + writeTaggedQFixed(QFontEngineQPA::Tag_LineThickness, fe->lineThickness()); + writeTaggedQFixed(QFontEngineQPA::Tag_MinLeftBearing, QFixed::fromReal(fe->minLeftBearing())); + writeTaggedQFixed(QFontEngineQPA::Tag_MinRightBearing, QFixed::fromReal(fe->minRightBearing())); + writeTaggedQFixed(QFontEngineQPA::Tag_UnderlinePosition, fe->underlinePosition()); + writeTaggedUInt8(QFontEngineQPA::Tag_PixelSize, fe->fontDef.pixelSize); + writeTaggedUInt8(QFontEngineQPA::Tag_Weight, fe->fontDef.weight); + writeTaggedUInt8(QFontEngineQPA::Tag_Style, fe->fontDef.style); + + writeTaggedUInt8(QFontEngineQPA::Tag_GlyphFormat, QFontEngineQPA::AlphamapGlyphs); + + writeTaggedString(QFontEngineQPA::Tag_EndOfHeader, QByteArray()); + align4(); + + const quint64 size = dev->pos(); + header.dataSize = qToBigEndian(size - sizeof(header)); + dev->seek(0); + dev->write((const char *)&header, sizeof(header)); + dev->seek(size); +} + +void QPAGenerator::writeGMap() +{ + const quint16 glyphCount = fe->glyphCount(); + + writeUInt16(QFontEngineQPA::GMapBlock); + writeUInt16(0); // padding + writeUInt32(glyphCount * 4); + + QByteArray &buffer = dev->buffer(); + const int numBytes = glyphCount * sizeof(quint32); + qint64 pos = buffer.size(); + buffer.resize(pos + numBytes); + qMemSet(buffer.data() + pos, 0xff, numBytes); + dev->seek(pos + numBytes); +} + +void QPAGenerator::writeBlock(QFontEngineQPA::BlockTag tag, const QByteArray &data) +{ + writeUInt16(tag); + writeUInt16(0); // padding + const int padSize = ((data.size() + 3) / 4) * 4 - data.size(); + writeUInt32(data.size() + padSize); + dev->write(data); + for (int i = 0; i < padSize; ++i) + writeUInt8(0); +} + +void QPAGenerator::writeTaggedString(QFontEngineQPA::HeaderTag tag, const QByteArray &string) +{ + writeUInt16(tag); + writeUInt16(string.length()); + dev->write(string); +} + +void QPAGenerator::writeTaggedUInt32(QFontEngineQPA::HeaderTag tag, quint32 value) +{ + writeUInt16(tag); + writeUInt16(sizeof(value)); + writeUInt32(value); +} + +void QPAGenerator::writeTaggedUInt8(QFontEngineQPA::HeaderTag tag, quint8 value) +{ + writeUInt16(tag); + writeUInt16(sizeof(value)); + writeUInt8(value); +} + +void QPAGenerator::writeTaggedQFixed(QFontEngineQPA::HeaderTag tag, QFixed value) +{ + writeUInt16(tag); + writeUInt16(sizeof(quint32)); + writeUInt32(value.value()); +} + + +/* + Creates a new multi QPA engine. + + This function takes ownership of the QFontEngine, increasing it's refcount. +*/ +QFontEngineMultiQPA::QFontEngineMultiQPA(QFontEngine *fe, int _script, const QStringList &fallbacks) + : QFontEngineMulti(fallbacks.size() + 1), + fallbackFamilies(fallbacks), script(_script) +{ + engines[0] = fe; + fe->ref.ref(); + fontDef = engines[0]->fontDef; +} + +void QFontEngineMultiQPA::loadEngine(int at) +{ + Q_ASSERT(at < engines.size()); + Q_ASSERT(engines.at(at) == 0); + + QFontDef request = fontDef; + request.styleStrategy |= QFont::NoFontMerging; + request.family = fallbackFamilies.at(at-1); + engines[at] = QFontDatabase::findFont(script, + /*fontprivate*/0, + request); + Q_ASSERT(engines[at]); + engines[at]->ref.ref(); + engines[at]->fontDef = request; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_qpa_p.h b/src/gui/text/qfontengine_qpa_p.h new file mode 100644 index 0000000000..e15beae36f --- /dev/null +++ b/src/gui/text/qfontengine_qpa_p.h @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_QPA_P_H +#define QFONTENGINE_QPA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// +#include +#include +#include +#include + +#include "qfontengine_p.h" + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QFontEngine; +class QFreetypeFace; +class QBuffer; + +class Q_GUI_EXPORT QFontEngineQPA : public QFontEngine +{ +public: + // if you add new tags please make sure to update the tables in + // qpfutil.cpp and tools/makeqpf/qpf2.cpp + enum HeaderTag { + Tag_FontName, // 0 string + Tag_FileName, // 1 string + Tag_FileIndex, // 2 quint32 + Tag_FontRevision, // 3 quint32 + Tag_FreeText, // 4 string + Tag_Ascent, // 5 QFixed + Tag_Descent, // 6 QFixed + Tag_Leading, // 7 QFixed + Tag_XHeight, // 8 QFixed + Tag_AverageCharWidth, // 9 QFixed + Tag_MaxCharWidth, // 10 QFixed + Tag_LineThickness, // 11 QFixed + Tag_MinLeftBearing, // 12 QFixed + Tag_MinRightBearing, // 13 QFixed + Tag_UnderlinePosition, // 14 QFixed + Tag_GlyphFormat, // 15 quint8 + Tag_PixelSize, // 16 quint8 + Tag_Weight, // 17 quint8 + Tag_Style, // 18 quint8 + Tag_EndOfHeader, // 19 string + Tag_WritingSystems, // 20 bitfield + + NumTags + }; + + enum TagType { + StringType, + FixedType, + UInt8Type, + UInt32Type, + BitFieldType + }; + + struct Tag + { + quint16 tag; + quint16 size; + }; + + enum GlyphFormat { + BitmapGlyphs = 1, + AlphamapGlyphs = 8 + }; + + enum { + CurrentMajorVersion = 2, + CurrentMinorVersion = 0 + }; + + // The CMap is identical to the TrueType CMap table format + // The GMap table is a normal array with the total number of + // covered glyphs in the TrueType font + enum BlockTag { + CMapBlock, + GMapBlock, + GlyphBlock + }; + + struct Q_PACKED Header + { + char magic[4]; // 'QPF2' + quint32 lock; // values: 0 = unlocked, 0xffffffff = read-only, otherwise qws client id of locking process + quint8 majorVersion; + quint8 minorVersion; + quint16 dataSize; + }; + + struct Q_PACKED Block + { + quint16 tag; + quint16 pad; + quint32 dataSize; + }; + + struct Q_PACKED Glyph + { + quint8 width; + quint8 height; + quint8 bytesPerLine; + qint8 x; + qint8 y; + qint8 advance; + }; + + QFontEngineQPA(const QFontDef &def, const QByteArray &data); + ~QFontEngineQPA(); + + FaceId faceId() const { return face_id; } + bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + + bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags); + QImage alphaMapForGlyph(glyph_t t); + + glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + glyph_metrics_t boundingBox(glyph_t glyph); + + QFixed ascent() const; + QFixed descent() const; + QFixed leading() const; + qreal maxCharWidth() const; + qreal minLeftBearing() const; + qreal minRightBearing() const; + QFixed underlinePosition() const; + QFixed lineThickness() const; + + Type type() const; + + bool canRender(const QChar *string, int len); + inline const char *name() const { return "QPF2"; } + + virtual int glyphCount() const { return glyphMapEntries; } + + bool isValid() const; + + const Glyph *findGlyph(glyph_t g) const; + + static bool verifyHeader(const uchar *data, int size); + static QVariant extractHeaderField(const uchar *data, HeaderTag tag); + +private: + + const uchar *fontData; + int dataSize; + const uchar *externalCMap; + quint32 cmapOffset; + int cmapSize; + quint32 glyphMapOffset; + quint32 glyphMapEntries; + quint32 glyphDataOffset; + quint32 glyphDataSize; + QString internalFileName; + QString encodedFileName; + bool readOnly; + + FaceId face_id; + QByteArray freetypeCMapTable; + mutable bool kerning_pairs_loaded; +}; + +struct QPAGenerator +{ + QPAGenerator(QBuffer *device, QFontEngine *engine) + : dev(device), fe(engine) {} + + void generate(); + void writeHeader(); + void writeGMap(); + void writeBlock(QFontEngineQPA::BlockTag tag, const QByteArray &data); + + void writeTaggedString(QFontEngineQPA::HeaderTag tag, const QByteArray &string); + void writeTaggedUInt32(QFontEngineQPA::HeaderTag tag, quint32 value); + void writeTaggedUInt8(QFontEngineQPA::HeaderTag tag, quint8 value); + void writeTaggedQFixed(QFontEngineQPA::HeaderTag tag, QFixed value); + + void writeUInt16(quint16 value) { value = qToBigEndian(value); dev->write((const char *)&value, sizeof(value)); } + void writeUInt32(quint32 value) { value = qToBigEndian(value); dev->write((const char *)&value, sizeof(value)); } + void writeUInt8(quint8 value) { dev->write((const char *)&value, sizeof(value)); } + void writeInt8(qint8 value) { dev->write((const char *)&value, sizeof(value)); } + + void align4() { while (dev->pos() & 3) { dev->putChar('\0'); } } + + QBuffer *dev; + QFontEngine *fe; +}; + +class QFontEngineMultiQPA : public QFontEngineMulti +{ +public: + QFontEngineMultiQPA(QFontEngine *fe, int script, const QStringList &fallbacks); + + void loadEngine(int at); + +private: + QStringList fallbackFamilies; + int script; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFONTENGINE_QPA_P_H diff --git a/src/gui/text/qfontengine_qpf.cpp b/src/gui/text/qfontengine_qpf.cpp new file mode 100644 index 0000000000..d35bbe506c --- /dev/null +++ b/src/gui/text/qfontengine_qpf.cpp @@ -0,0 +1,1220 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontengine_qpf_p.h" + +#include "private/qpaintengine_raster_p.h" +#include +#include + +#include +#include +#include +#if !defined(QT_NO_FREETYPE) +#include "private/qfontengine_ft_p.h" +#endif +#include "private/qcore_unix_p.h" // overrides QT_OPEN + +// for mmap +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_QWS_QPF2 + +#include "qpfutil.cpp" + +QT_BEGIN_INCLUDE_NAMESPACE + +#if defined(Q_WS_QWS) +# include "private/qwscommand_qws_p.h" +# include "qwsdisplay_qws.h" +# include "qabstractfontengine_p.h" +#endif +#include "qplatformdefs.h" +QT_END_INCLUDE_NAMESPACE + +//#define DEBUG_HEADER +//#define DEBUG_FONTENGINE + +#if defined(DEBUG_HEADER) +# define DEBUG_VERIFY qDebug +#else +# define DEBUG_VERIFY if (0) qDebug +#endif + +#define READ_VERIFY(type, variable) \ + if (tagPtr + sizeof(type) > endPtr) { \ + DEBUG_VERIFY() << "read verify failed in line" << __LINE__; \ + return 0; \ + } \ + variable = qFromBigEndian(tagPtr); \ + DEBUG_VERIFY() << "read value" << variable << "of type " #type; \ + tagPtr += sizeof(type) + +template +T readValue(const uchar *&data) +{ + T value = qFromBigEndian(data); + data += sizeof(T); + return value; +} + +#define VERIFY(condition) \ + if (!(condition)) { \ + DEBUG_VERIFY() << "condition " #condition " failed in line" << __LINE__; \ + return 0; \ + } + +#define VERIFY_TAG(condition) \ + if (!(condition)) { \ + DEBUG_VERIFY() << "verifying tag condition " #condition " failed in line" << __LINE__ << "with tag" << tag; \ + return 0; \ + } + +static inline const uchar *verifyTag(const uchar *tagPtr, const uchar *endPtr) +{ + quint16 tag, length; + READ_VERIFY(quint16, tag); + READ_VERIFY(quint16, length); + if (tag == QFontEngineQPF::Tag_EndOfHeader) + return endPtr; + if (tag < QFontEngineQPF::NumTags) { + switch (tagTypes[tag]) { + case QFontEngineQPF::BitFieldType: + case QFontEngineQPF::StringType: + // can't do anything... + break; + case QFontEngineQPF::UInt32Type: + VERIFY_TAG(length == sizeof(quint32)); + break; + case QFontEngineQPF::FixedType: + VERIFY_TAG(length == sizeof(quint32)); + break; + case QFontEngineQPF::UInt8Type: + VERIFY_TAG(length == sizeof(quint8)); + break; + } +#if defined(DEBUG_HEADER) + if (length == 1) + qDebug() << "tag data" << hex << *tagPtr; + else if (length == 4) + qDebug() << "tag data" << hex << tagPtr[0] << tagPtr[1] << tagPtr[2] << tagPtr[3]; +#endif + } + return tagPtr + length; +} + +const QFontEngineQPF::Glyph *QFontEngineQPF::findGlyph(glyph_t g) const +{ + if (!g || g >= glyphMapEntries) + return 0; + const quint32 *gmapPtr = reinterpret_cast(fontData + glyphMapOffset); + quint32 glyphPos = qFromBigEndian(gmapPtr[g]); + if (glyphPos > glyphDataSize) { + if (glyphPos == 0xffffffff) + return 0; +#if defined(DEBUG_FONTENGINE) + qDebug() << "glyph" << g << "outside of glyphData, remapping font file"; +#endif +#if !defined(QT_NO_FREETYPE) && !defined(QT_FONTS_ARE_RESOURCES) + const_cast(this)->remapFontData(); +#endif + if (glyphPos > glyphDataSize) + return 0; + } + return reinterpret_cast(fontData + glyphDataOffset + glyphPos); +} + +bool QFontEngineQPF::verifyHeader(const uchar *data, int size) +{ + VERIFY(size >= int(sizeof(Header))); + const Header *header = reinterpret_cast(data); + if (header->magic[0] != 'Q' + || header->magic[1] != 'P' + || header->magic[2] != 'F' + || header->magic[3] != '2') + return false; + + VERIFY(header->majorVersion <= CurrentMajorVersion); + const quint16 dataSize = qFromBigEndian(header->dataSize); + VERIFY(size >= int(sizeof(Header)) + dataSize); + + const uchar *tagPtr = data + sizeof(Header); + const uchar *tagEndPtr = tagPtr + dataSize; + while (tagPtr < tagEndPtr - 3) { + tagPtr = verifyTag(tagPtr, tagEndPtr); + VERIFY(tagPtr); + } + + VERIFY(tagPtr <= tagEndPtr); + return true; +} + +QVariant QFontEngineQPF::extractHeaderField(const uchar *data, HeaderTag requestedTag) +{ + const Header *header = reinterpret_cast(data); + const uchar *tagPtr = data + sizeof(Header); + const uchar *endPtr = tagPtr + qFromBigEndian(header->dataSize); + while (tagPtr < endPtr - 3) { + quint16 tag = readValue(tagPtr); + quint16 length = readValue(tagPtr); + if (tag == requestedTag) { + switch (tagTypes[requestedTag]) { + case StringType: + return QVariant(QString::fromUtf8(reinterpret_cast(tagPtr), length)); + case UInt32Type: + return QVariant(readValue(tagPtr)); + case UInt8Type: + return QVariant(uint(*tagPtr)); + case FixedType: + return QVariant(QFixed::fromFixed(readValue(tagPtr)).toReal()); + case BitFieldType: + return QVariant(QByteArray(reinterpret_cast(tagPtr), length)); + } + return QVariant(); + } else if (tag == Tag_EndOfHeader) { + break; + } + tagPtr += length; + } + + return QVariant(); +} + +#endif // QT_NO_QWS_QPF2 + +QString qws_fontCacheDir() +{ + QString dir; +#if defined(Q_WS_QWS) + extern QString qws_dataDir(); + dir = qws_dataDir(); +#else + dir = QDir::tempPath(); +#endif + dir.append(QLatin1String("/fonts/")); + QDir qd(dir); + if (!qd.exists() && !qd.mkpath(dir)) + dir = QDir::tempPath(); + return dir; +} + +#ifndef QT_NO_QWS_QPF2 + +#ifndef QT_FONTS_ARE_RESOURCES +QList QFontEngineQPF::cleanUpAfterClientCrash(const QList &crashedClientIds) +{ + QList removedFonts; + QDir dir(qws_fontCacheDir(), QLatin1String("*.qsf")); + for (int i = 0; i < int(dir.count()); ++i) { + const QByteArray fileName = QFile::encodeName(dir.absoluteFilePath(dir[i])); + + int fd = QT_OPEN(fileName.constData(), O_RDONLY, 0); + if (fd >= 0) { + void *header = ::mmap(0, sizeof(QFontEngineQPF::Header), PROT_READ, MAP_SHARED, fd, 0); + if (header && header != MAP_FAILED) { + quint32 lockValue = reinterpret_cast(header)->lock; + + if (lockValue && crashedClientIds.contains(lockValue)) { + removedFonts.append(fileName); + QFile::remove(QFile::decodeName(fileName)); + } + + ::munmap(header, sizeof(QFontEngineQPF::Header)); + } + QT_CLOSE(fd); + } + } + if (!removedFonts.isEmpty()) + qDebug() << "list of corrupted and removed fonts:" << removedFonts; + return removedFonts; +} +#endif + +static inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} +#ifdef QT_FONTS_ARE_RESOURCES +QFontEngineQPF::QFontEngineQPF(const QFontDef &def, const uchar *bytes, int size) + : fd(-1), fontData(bytes), dataSize(size), renderingFontEngine(0) +#else +QFontEngineQPF::QFontEngineQPF(const QFontDef &def, int fileDescriptor, QFontEngine *fontEngine) + : fd(fileDescriptor), fontData(0), dataSize(0), renderingFontEngine(fontEngine) +#endif +{ + fontDef = def; + cache_cost = 100; + freetype = 0; + externalCMap = 0; + cmapOffset = 0; + cmapSize = 0; + glyphMapOffset = 0; + glyphMapEntries = 0; + glyphDataOffset = 0; + glyphDataSize = 0; + if (renderingFontEngine) + glyphFormat = renderingFontEngine->glyphFormat; + kerning_pairs_loaded = false; + readOnly = true; + +#if defined(DEBUG_FONTENGINE) + qDebug() << "QFontEngineQPF::QFontEngineQPF( fd =" << fd << ", renderingFontEngine =" << renderingFontEngine << ')'; +#endif + +#ifndef QT_FONTS_ARE_RESOURCES + if (fd < 0) { + if (!renderingFontEngine) + return; + + fileName = fontDef.family.toLower() + QLatin1Char('_') + + QString::number(fontDef.pixelSize) + + QLatin1Char('_') + QString::number(fontDef.weight) + + (fontDef.style != QFont::StyleNormal ? + QLatin1String("_italic") : QLatin1String("")) + + QLatin1String(".qsf"); + fileName.replace(QLatin1Char(' '), QLatin1Char('_')); + fileName.prepend(qws_fontCacheDir()); + + encodedFileName = QFile::encodeName(fileName); + if (::access(encodedFileName, F_OK) == 0) { +#if defined(DEBUG_FONTENGINE) + qDebug() << "found existing qpf:" << fileName; +#endif + if (::access(encodedFileName, W_OK | R_OK) == 0) { + fd = QT_OPEN(encodedFileName, O_RDWR); + } + // read-write access failed - try read-only access + if (fd == -1 && ::access(encodedFileName, R_OK) == 0) { + fd = QT_OPEN(encodedFileName, O_RDONLY); + if (fd == -1) { +#if defined(DEBUG_FONTENGINE) + qErrnoWarning("QFontEngineQPF: unable to open %s", encodedName.constData()); +#endif + return; + } + } + if (fd == -1) { +#if defined(DEBUG_FONTENGINE) + qWarning("QFontEngineQPF: insufficient access rights to %s", encodedName.constData()); +#endif + return; + } + } else { +#if defined(DEBUG_FONTENGINE) + qDebug() << "creating qpf on the fly:" << fileName; +#endif + if (::access(QFile::encodeName(qws_fontCacheDir()), W_OK) == 0) { + fd = QT_OPEN(encodedFileName, O_RDWR | O_EXCL | O_CREAT, 0644); + if (fd == -1) { +#if defined(DEBUG_FONTENGINE) + qErrnoWarning(errno, "QFontEngineQPF: open() failed for %s", encodedName.constData()); +#endif + return; + } + + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + QPFGenerator generator(&buffer, renderingFontEngine); + generator.generate(); + buffer.close(); + const QByteArray &data = buffer.data(); + if (QT_WRITE(fd, data.constData(), data.size()) == -1) { +#if defined(DEBUG_FONTENGINE) + qErrnoWarning(errno, "QFontEngineQPF: write() failed for %s", encodedName.constData()); +#endif + return; + } + } else { +#if defined(DEBUG_FONTENGINE) + qErrnoWarning(errno, "QFontEngineQPF: access() failed for %s", qPrintable(qws_fontCacheDir())); +#endif + return; + } + } + } + + QT_STATBUF st; + if (QT_FSTAT(fd, &st)) { +#if defined(DEBUG_FONTENGINE) + qErrnoWarning(errno, "QFontEngineQPF: fstat failed!"); +#endif + return; + } + dataSize = st.st_size; + + + fontData = (const uchar *)::mmap(0, st.st_size, PROT_READ | (renderingFontEngine ? PROT_WRITE : 0), MAP_SHARED, fd, 0); + if (!fontData || fontData == (const uchar *)MAP_FAILED) { +#if defined(DEBUG_FONTENGINE) + perror("mmap failed"); +#endif + fontData = 0; + return; + } +#endif //QT_FONTS_ARE_RESOURCES + + if (!verifyHeader(fontData, dataSize)) { +#if defined(DEBUG_FONTENGINE) + qDebug() << "verifyHeader failed!"; +#endif + return; + } + + const Header *header = reinterpret_cast(fontData); + + readOnly = (header->lock == 0xffffffff); + + const uchar *data = fontData + sizeof(Header) + qFromBigEndian(header->dataSize); + const uchar *endPtr = fontData + dataSize; + while (data <= endPtr - 8) { + quint16 blockTag = readValue(data); + data += 2; // skip padding + quint32 blockSize = readValue(data); + + if (blockTag == CMapBlock) { + cmapOffset = data - fontData; + cmapSize = blockSize; + } else if (blockTag == GMapBlock) { + glyphMapOffset = data - fontData; + glyphMapEntries = blockSize / 4; + } else if (blockTag == GlyphBlock) { + glyphDataOffset = data - fontData; + glyphDataSize = blockSize; + } + + data += blockSize; + } + + face_id.filename = QFile::encodeName(extractHeaderField(fontData, Tag_FileName).toString()); + face_id.index = extractHeaderField(fontData, Tag_FileIndex).toInt(); +#if !defined(QT_NO_FREETYPE) && !defined(QT_FONTS_ARE_RESOURCES) + freetype = QFreetypeFace::getFace(face_id); + if (!freetype) { + QString newPath = +#ifndef QT_NO_SETTINGS + QLibraryInfo::location(QLibraryInfo::LibrariesPath) + +#endif + QLatin1String("/fonts/") + + QFileInfo(QFile::decodeName(face_id.filename)).fileName(); + face_id.filename = QFile::encodeName(newPath); + freetype = QFreetypeFace::getFace(face_id); + } + if (freetype) { + const quint32 qpfTtfRevision = extractHeaderField(fontData, Tag_FontRevision).toUInt(); + uchar data[4]; + uint length = 4; + bool ok = freetype->getSfntTable(MAKE_TAG('h', 'e', 'a', 'd'), data, &length); + if (!ok || length != 4 + || qFromBigEndian(data) != qpfTtfRevision) { + freetype->release(face_id); + freetype = 0; + } + } + if (!cmapOffset && freetype) { + freetypeCMapTable = getSfntTable(MAKE_TAG('c', 'm', 'a', 'p')); + externalCMap = reinterpret_cast(freetypeCMapTable.constData()); + cmapSize = freetypeCMapTable.size(); + } +#endif + + // get the real cmap + if (cmapOffset) { + int tableSize = cmapSize; + const uchar *cmapPtr = getCMap(fontData + cmapOffset, tableSize, &symbol, &cmapSize); + if (cmapPtr) + cmapOffset = cmapPtr - fontData; + else + cmapOffset = 0; + } else if (externalCMap) { + int tableSize = cmapSize; + externalCMap = getCMap(externalCMap, tableSize, &symbol, &cmapSize); + } + + // verify all the positions in the glyphMap + if (glyphMapOffset) { + const quint32 *gmapPtr = reinterpret_cast(fontData + glyphMapOffset); + for (uint i = 0; i < glyphMapEntries; ++i) { + quint32 glyphDataPos = qFromBigEndian(gmapPtr[i]); + if (glyphDataPos == 0xffffffff) + continue; + if (glyphDataPos >= glyphDataSize) { + // error + glyphMapOffset = 0; + glyphMapEntries = 0; + break; + } + } + } + +#if defined(DEBUG_FONTENGINE) + if (!isValid()) + qDebug() << "fontData" << fontData << "dataSize" << dataSize + << "externalCMap" << externalCMap << "cmapOffset" << cmapOffset + << "glyphMapOffset" << glyphMapOffset << "glyphDataOffset" << glyphDataOffset + << "fd" << fd << "glyphDataSize" << glyphDataSize; +#endif +#if defined(Q_WS_QWS) + if (isValid() && renderingFontEngine) + qt_fbdpy->sendFontCommand(QWSFontCommand::StartedUsingFont, encodedFileName); +#endif +} + +QFontEngineQPF::~QFontEngineQPF() +{ +#if defined(Q_WS_QWS) + if (isValid() && renderingFontEngine) { + QT_TRY { + qt_fbdpy->sendFontCommand(QWSFontCommand::StoppedUsingFont, encodedFileName); + } QT_CATCH(...) { + qDebug("QFontEngineQPF::~QFontEngineQPF: Out of memory"); + // ignore. + } + } +#endif + delete renderingFontEngine; + if (fontData) { + if (munmap((void *)fontData, dataSize) == -1) { +#if defined(DEBUG_FONTENGINE) + qErrnoWarning(errno, "~QFontEngineQPF: Unable to munmap"); +#endif + } + } + if (fd != -1) + ::close(fd); +#if !defined(QT_NO_FREETYPE) + if (freetype) + freetype->release(face_id); +#endif +} + +bool QFontEngineQPF::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ +#if !defined(QT_NO_FREETYPE) + if (freetype) + return freetype->getSfntTable(tag, buffer, length); +#endif + Q_UNUSED(tag); + Q_UNUSED(buffer); + *length = 0; + return false; +} + +bool QFontEngineQPF::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (!externalCMap && !cmapOffset && renderingFontEngine) { + if (!renderingFontEngine->stringToCMap(str, len, glyphs, nglyphs, flags)) + return false; +#ifndef QT_NO_FREETYPE + const_cast(this)->ensureGlyphsLoaded(*glyphs); +#endif + return true; + } + + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + +#if defined(DEBUG_FONTENGINE) + QSet seenGlyphs; +#endif + + const uchar *cmap = externalCMap ? externalCMap : (fontData + cmapOffset); + + bool mirrored = flags & QTextEngine::RightToLeft; + int glyph_pos = 0; + if (symbol) { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc); + if(!glyphs->glyphs[glyph_pos] && uc < 0x100) + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + ++glyph_pos; + } + } else { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + if (mirrored) + uc = QChar::mirroredChar(uc); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc); +#if 0 && defined(DEBUG_FONTENGINE) + QChar c(uc); + if (!findGlyph(glyphs[glyph_pos].glyph) && !seenGlyphs.contains(c)) + qDebug() << "glyph for character" << c << '/' << hex << uc << "is" << dec << glyphs[glyph_pos].glyph; + + seenGlyphs.insert(c); +#endif + ++glyph_pos; + } + } + + *nglyphs = glyph_pos; + glyphs->numGlyphs = glyph_pos; + recalcAdvances(glyphs, flags); + return true; +} + +void QFontEngineQPF::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const +{ +#ifndef QT_NO_FREETYPE + const_cast(this)->ensureGlyphsLoaded(*glyphs); +#endif + for (int i = 0; i < glyphs->numGlyphs; ++i) { + const Glyph *g = findGlyph(glyphs->glyphs[i]); + if (!g) { + glyphs->glyphs[i] = 0; + continue; + } + glyphs->advances_x[i] = g->advance; + glyphs->advances_y[i] = 0; + } +} + +QImage QFontEngineQPF::alphaMapForGlyph(glyph_t g) +{ + const Glyph *glyph = findGlyph(g); + if (!glyph) + return QImage(); + + const uchar *bits = ((const uchar *) glyph) + sizeof(Glyph); + + QImage image(glyph->width, glyph->height, QImage::Format_Indexed8); + for (int j=0; j<256; ++j) + image.setColor(j, qRgba(0, 0, 0, j)); + + for (int i=0; iheight; ++i) { + memcpy(image.scanLine(i), bits, glyph->bytesPerLine); + bits += glyph->bytesPerLine; + } + return image; +} + +void QFontEngineQPF::draw(QPaintEngine *p, qreal _x, qreal _y, const QTextItemInt &si) +{ + QPaintEngineState *pState = p->state; + QRasterPaintEngine *paintEngine = static_cast(p); + + QTransform matrix = pState->transform(); + matrix.translate(_x, _y); + QFixed x = QFixed::fromReal(matrix.dx()); + QFixed y = QFixed::fromReal(matrix.dy()); + + QVarLengthArray positions; + QVarLengthArray glyphs; + getGlyphPositions(si.glyphs, matrix, si.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + for(int i = 0; i < glyphs.size(); i++) { + const Glyph *glyph = findGlyph(glyphs[i]); + if (!glyph) + continue; + + const int depth = 8; //### + + paintEngine->alphaPenBlt(reinterpret_cast(glyph) + sizeof(Glyph), glyph->bytesPerLine, depth, + qRound(positions[i].x) + glyph->x, + qRound(positions[i].y) + glyph->y, + glyph->width, glyph->height); + } +} + +void QFontEngineQPF::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (renderingFontEngine && + (renderingFontEngine->type() != QFontEngine::Proxy + || static_cast(renderingFontEngine)->capabilities() & QAbstractFontEngine::CanOutlineGlyphs)) { + renderingFontEngine->addOutlineToPath(x, y, glyphs, path, flags); + return; + } + addBitmapFontToPath(x, y, glyphs, path, flags); +} + +glyph_metrics_t QFontEngineQPF::boundingBox(const QGlyphLayout &glyphs) +{ +#ifndef QT_NO_FREETYPE + const_cast(this)->ensureGlyphsLoaded(glyphs); +#endif + + glyph_metrics_t overall; + // initialize with line height, we get the same behaviour on all platforms + overall.y = -ascent(); + overall.height = ascent() + descent() + 1; + + QFixed ymax = 0; + QFixed xmax = 0; + for (int i = 0; i < glyphs.numGlyphs; i++) { + const Glyph *g = findGlyph(glyphs.glyphs[i]); + if (!g) + continue; + + QFixed x = overall.xoff + glyphs.offsets[i].x + g->x; + QFixed y = overall.yoff + glyphs.offsets[i].y + g->y; + overall.x = qMin(overall.x, x); + overall.y = qMin(overall.y, y); + xmax = qMax(xmax, x + g->width); + ymax = qMax(ymax, y + g->height); + overall.xoff += g->advance; + } + overall.height = qMax(overall.height, ymax - overall.y); + overall.width = xmax - overall.x; + + return overall; +} + +glyph_metrics_t QFontEngineQPF::boundingBox(glyph_t glyph) +{ +#ifndef QT_NO_FREETYPE + { + QGlyphLayoutArray<1> tmp; + tmp.glyphs[0] = glyph; + const_cast(this)->ensureGlyphsLoaded(tmp); + } +#endif + glyph_metrics_t overall; + const Glyph *g = findGlyph(glyph); + if (!g) + return overall; + overall.x = g->x; + overall.y = g->y; + overall.width = g->width; + overall.height = g->height; + overall.xoff = g->advance; + return overall; +} + +QFixed QFontEngineQPF::ascent() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_Ascent).value()); +} + +QFixed QFontEngineQPF::descent() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_Descent).value()); +} + +QFixed QFontEngineQPF::leading() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_Leading).value()); +} + +qreal QFontEngineQPF::maxCharWidth() const +{ + return extractHeaderField(fontData, Tag_MaxCharWidth).value(); +} + +qreal QFontEngineQPF::minLeftBearing() const +{ + return extractHeaderField(fontData, Tag_MinLeftBearing).value(); +} + +qreal QFontEngineQPF::minRightBearing() const +{ + return extractHeaderField(fontData, Tag_MinRightBearing).value(); +} + +QFixed QFontEngineQPF::underlinePosition() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_UnderlinePosition).value()); +} + +QFixed QFontEngineQPF::lineThickness() const +{ + return QFixed::fromReal(extractHeaderField(fontData, Tag_LineThickness).value()); +} + +QFontEngine::Type QFontEngineQPF::type() const +{ + return QFontEngine::QPF2; +} + +bool QFontEngineQPF::canRender(const QChar *string, int len) +{ + const uchar *cmap = externalCMap ? externalCMap : (fontData + cmapOffset); + + if (symbol) { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(string, i, len); + glyph_t g = getTrueTypeGlyphIndex(cmap, uc); + if(!g && uc < 0x100) + g = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + if (!g) + return false; + } + } else { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(string, i, len); + if (!getTrueTypeGlyphIndex(cmap, uc)) + return false; + } + } + return true; +} + +bool QFontEngineQPF::isValid() const +{ + return fontData && dataSize && (cmapOffset || externalCMap || renderingFontEngine) + && glyphMapOffset && glyphDataOffset && (fd >= 0 || glyphDataSize > 0); +} + +#if !defined(QT_NO_FREETYPE) +FT_Face QFontEngineQPF::lockFace() const +{ + Q_ASSERT(freetype); + freetype->lock(); + FT_Face face = freetype->face; + + // ### not perfect + const int ysize = qRound(fontDef.pixelSize * qreal(64)); + const int xsize = ysize; + + if (freetype->xsize != xsize || freetype->ysize != ysize) { + FT_Set_Char_Size(face, xsize, ysize, 0, 0); + freetype->xsize = xsize; + freetype->ysize = ysize; + } + FT_Matrix identityMatrix; + identityMatrix.xx = 0x10000; + identityMatrix.yy = 0x10000; + identityMatrix.xy = 0; + identityMatrix.yx = 0; + if (freetype->matrix.xx != identityMatrix.xx || + freetype->matrix.yy != identityMatrix.yy || + freetype->matrix.xy != identityMatrix.xy || + freetype->matrix.yx != identityMatrix.yx) { + freetype->matrix = identityMatrix; + FT_Set_Transform(face, &freetype->matrix, 0); + } + return face; +} + +void QFontEngineQPF::unlockFace() const +{ + freetype->unlock(); +} + +void QFontEngineQPF::doKerning(QGlyphLayout *g, QTextEngine::ShaperFlags flags) const +{ + if (!kerning_pairs_loaded) { + kerning_pairs_loaded = true; + if (freetype) { + lockFace(); + if (freetype->face->size->metrics.x_ppem != 0) { + QFixed scalingFactor(freetype->face->units_per_EM/freetype->face->size->metrics.x_ppem); + unlockFace(); + const_cast(this)->loadKerningPairs(scalingFactor); + } else { + unlockFace(); + } + } + } + QFontEngine::doKerning(g, flags); +} + +HB_Error QFontEngineQPF::getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints) +{ + if (!freetype) + return HB_Err_Not_Covered; + lockFace(); + HB_Error result = freetype->getPointInOutline(glyph, flags, point, xpos, ypos, nPoints); + unlockFace(); + return result; +} + +QFixed QFontEngineQPF::emSquareSize() const +{ + if (!freetype) + return QFontEngine::emSquareSize(); + if (FT_IS_SCALABLE(freetype->face)) + return freetype->face->units_per_EM; + else + return freetype->face->size->metrics.y_ppem; +} + +void QFontEngineQPF::ensureGlyphsLoaded(const QGlyphLayout &glyphs) +{ + if (readOnly) + return; + bool locked = false; + for (int i = 0; i < glyphs.numGlyphs; ++i) { + if (!glyphs.glyphs[i]) + continue; + const Glyph *g = findGlyph(glyphs.glyphs[i]); + if (g) + continue; + if (!locked) { + if (!lockFile()) + return; + locked = true; + g = findGlyph(glyphs.glyphs[i]); + if (g) + continue; + } + loadGlyph(glyphs.glyphs[i]); + } + if (locked) { + unlockFile(); +#if defined(DEBUG_FONTENGINE) + qDebug() << "Finished rendering glyphs\n"; +#endif + } +} + +void QFontEngineQPF::loadGlyph(glyph_t glyph) +{ + quint32 glyphPos = ~0; + + if (!renderingFontEngine) + return; + QImage img = renderingFontEngine->alphaMapForGlyph(glyph); + if (img.format() != QImage::Format_Indexed8) { + bool mono = img.depth() == 1; + img = img.convertToFormat(QImage::Format_Indexed8); + if (mono) { + //### we know that 1 is opaque and 0 is transparent + uchar *byte = img.bits(); + int count = img.byteCount(); + while (count--) + *byte++ *= 0xff; + } + } + glyph_metrics_t metrics = renderingFontEngine->boundingBox(glyph); + renderingFontEngine->removeGlyphFromCache(glyph); + + off_t oldSize = ::lseek(fd, 0, SEEK_END); + if (oldSize == (off_t)-1) + return; + + Glyph g; + g.width = img.width(); + g.height = img.height(); + g.bytesPerLine = img.bytesPerLine(); + g.x = qRound(metrics.x); + g.y = qRound(metrics.y); + g.advance = qRound(metrics.xoff); + + QT_WRITE(fd, &g, sizeof(g)); + QT_WRITE(fd, img.bits(), img.byteCount()); + + glyphPos = oldSize - glyphDataOffset; +#if 0 && defined(DEBUG_FONTENGINE) + qDebug() << "glyphPos for new glyph" << glyph << "is" << glyphPos << "oldSize" << oldSize << "glyphDataOffset" << glyphDataOffset; +#endif + + quint32 *gmap = (quint32 *)(fontData + glyphMapOffset); + gmap[glyph] = qToBigEndian(glyphPos); + + glyphDataSize = glyphPos + sizeof(g) + img.byteCount(); + quint32 *blockSizePtr = (quint32 *)(fontData + glyphDataOffset - 4); + *blockSizePtr = qToBigEndian(glyphDataSize); +} + +bool QFontEngineQPF::lockFile() +{ + // #### this does not handle the case when the process holding the + // lock hangs for some reason + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; // lock the whole file + while (fcntl(fd, F_SETLKW, &lock) != 0) { + if (errno == EINTR) + continue; + perror("locking qpf"); + return false; + } + Header *header = (Header *)fontData; + if (header->lock) { + lock.l_type = F_UNLCK; + if (fcntl(fd, F_SETLK, &lock) != 0) + perror("unlocking possibly corrupt qpf"); + return false; + } +#if defined(Q_WS_QWS) + extern int qws_client_id; + // qws_client_id == 0 means we're the server. in this case we just + // set the id to 1 + header->lock = qws_client_id ? qws_client_id : 1; +#else + header->lock = 1; +#endif + return true; +} + +void QFontEngineQPF::unlockFile() +{ + ((Header *)fontData)->lock = 0; + + struct flock lock; + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; // lock the whole file + if (fcntl(fd, F_SETLK, &lock) != 0) { + perror("unlocking qpf"); + } + + remapFontData(); +} + +void QFontEngineQPF::remapFontData() +{ + off_t newFileSize = ::lseek(fd, 0, SEEK_END); + if (newFileSize == (off_t)-1) { +#ifdef DEBUG_FONTENGINE + perror("QFontEngineQPF::remapFontData: lseek failed"); +#endif + fontData = 0; + return; + } + +#ifndef QT_NO_MREMAP + fontData = static_cast(::mremap(const_cast(fontData), dataSize, newFileSize, MREMAP_MAYMOVE)); + if (!fontData || fontData == (const uchar *)MAP_FAILED) { +# if defined(DEBUG_FONTENGINE) + perror("QFontEngineQPF::remapFontData(): mremap failed"); +# endif + fontData = 0; + } + + if (!fontData) +#endif // QT_NO_MREMAP + { + int status = ::munmap((void *)fontData, dataSize); + if (status != 0) + qErrnoWarning(status, "QFontEngineQPF::remapFomrData: munmap failed!"); + + fontData = (const uchar *)::mmap(0, newFileSize, PROT_READ | (renderingFontEngine ? PROT_WRITE : 0), + MAP_SHARED, fd, 0); + if (!fontData || fontData == (const uchar *)MAP_FAILED) { +# if defined(DEBUG_FONTENGINE) + perror("mmap failed"); +# endif + fontData = 0; + return; + } + } + + dataSize = newFileSize; + glyphDataSize = newFileSize - glyphDataOffset; +#if defined(DEBUG_FONTENGINE) + qDebug() << "remapped the font file to" << newFileSize << "bytes"; +#endif +} + +#endif // QT_NO_FREETYPE + +void QPFGenerator::generate() +{ + writeHeader(); + writeGMap(); + writeBlock(QFontEngineQPF::GlyphBlock, QByteArray()); + + dev->seek(4); // position of header.lock + writeUInt32(0); +} + +void QPFGenerator::writeHeader() +{ + QFontEngineQPF::Header header; + + header.magic[0] = 'Q'; + header.magic[1] = 'P'; + header.magic[2] = 'F'; + header.magic[3] = '2'; + header.lock = 1; + header.majorVersion = QFontEngineQPF::CurrentMajorVersion; + header.minorVersion = QFontEngineQPF::CurrentMinorVersion; + header.dataSize = 0; + dev->write((const char *)&header, sizeof(header)); + + writeTaggedString(QFontEngineQPF::Tag_FontName, fe->fontDef.family.toUtf8()); + + QFontEngine::FaceId face = fe->faceId(); + writeTaggedString(QFontEngineQPF::Tag_FileName, face.filename); + writeTaggedUInt32(QFontEngineQPF::Tag_FileIndex, face.index); + + { + uchar data[4]; + uint len = 4; + bool ok = fe->getSfntTableData(MAKE_TAG('h', 'e', 'a', 'd'), data, &len); + if (ok) { + const quint32 revision = qFromBigEndian(data); + writeTaggedUInt32(QFontEngineQPF::Tag_FontRevision, revision); + } + } + + writeTaggedQFixed(QFontEngineQPF::Tag_Ascent, fe->ascent()); + writeTaggedQFixed(QFontEngineQPF::Tag_Descent, fe->descent()); + writeTaggedQFixed(QFontEngineQPF::Tag_Leading, fe->leading()); + writeTaggedQFixed(QFontEngineQPF::Tag_XHeight, fe->xHeight()); + writeTaggedQFixed(QFontEngineQPF::Tag_AverageCharWidth, fe->averageCharWidth()); + writeTaggedQFixed(QFontEngineQPF::Tag_MaxCharWidth, QFixed::fromReal(fe->maxCharWidth())); + writeTaggedQFixed(QFontEngineQPF::Tag_LineThickness, fe->lineThickness()); + writeTaggedQFixed(QFontEngineQPF::Tag_MinLeftBearing, QFixed::fromReal(fe->minLeftBearing())); + writeTaggedQFixed(QFontEngineQPF::Tag_MinRightBearing, QFixed::fromReal(fe->minRightBearing())); + writeTaggedQFixed(QFontEngineQPF::Tag_UnderlinePosition, fe->underlinePosition()); + writeTaggedUInt8(QFontEngineQPF::Tag_PixelSize, fe->fontDef.pixelSize); + writeTaggedUInt8(QFontEngineQPF::Tag_Weight, fe->fontDef.weight); + writeTaggedUInt8(QFontEngineQPF::Tag_Style, fe->fontDef.style); + + writeTaggedUInt8(QFontEngineQPF::Tag_GlyphFormat, QFontEngineQPF::AlphamapGlyphs); + + writeTaggedString(QFontEngineQPF::Tag_EndOfHeader, QByteArray()); + align4(); + + const quint64 size = dev->pos(); + header.dataSize = qToBigEndian(size - sizeof(header)); + dev->seek(0); + dev->write((const char *)&header, sizeof(header)); + dev->seek(size); +} + +void QPFGenerator::writeGMap() +{ + const quint16 glyphCount = fe->glyphCount(); + + writeUInt16(QFontEngineQPF::GMapBlock); + writeUInt16(0); // padding + writeUInt32(glyphCount * 4); + + QByteArray &buffer = dev->buffer(); + const int numBytes = glyphCount * sizeof(quint32); + qint64 pos = buffer.size(); + buffer.resize(pos + numBytes); + qMemSet(buffer.data() + pos, 0xff, numBytes); + dev->seek(pos + numBytes); +} + +void QPFGenerator::writeBlock(QFontEngineQPF::BlockTag tag, const QByteArray &data) +{ + writeUInt16(tag); + writeUInt16(0); // padding + const int padSize = ((data.size() + 3) / 4) * 4 - data.size(); + writeUInt32(data.size() + padSize); + dev->write(data); + for (int i = 0; i < padSize; ++i) + writeUInt8(0); +} + +void QPFGenerator::writeTaggedString(QFontEngineQPF::HeaderTag tag, const QByteArray &string) +{ + writeUInt16(tag); + writeUInt16(string.length()); + dev->write(string); +} + +void QPFGenerator::writeTaggedUInt32(QFontEngineQPF::HeaderTag tag, quint32 value) +{ + writeUInt16(tag); + writeUInt16(sizeof(value)); + writeUInt32(value); +} + +void QPFGenerator::writeTaggedUInt8(QFontEngineQPF::HeaderTag tag, quint8 value) +{ + writeUInt16(tag); + writeUInt16(sizeof(value)); + writeUInt8(value); +} + +void QPFGenerator::writeTaggedQFixed(QFontEngineQPF::HeaderTag tag, QFixed value) +{ + writeUInt16(tag); + writeUInt16(sizeof(quint32)); + writeUInt32(value.value()); +} + +#endif // QT_NO_QWS_QPF2 + +/* + Creates a new multi qws engine. + + This function takes ownership of the QFontEngine, increasing it's refcount. +*/ +QFontEngineMultiQWS::QFontEngineMultiQWS(QFontEngine *fe, int _script, const QStringList &fallbacks) + : QFontEngineMulti(fallbacks.size() + 1), + fallbackFamilies(fallbacks), script(_script) +{ + engines[0] = fe; + fe->ref.ref(); + fontDef = engines[0]->fontDef; +} + +void QFontEngineMultiQWS::loadEngine(int at) +{ + Q_ASSERT(at < engines.size()); + Q_ASSERT(engines.at(at) == 0); + + QFontDef request = fontDef; + request.styleStrategy |= QFont::NoFontMerging; + request.family = fallbackFamilies.at(at-1); + engines[at] = QFontDatabase::findFont(script, + /*fontprivate*/0, + request); + Q_ASSERT(engines[at]); + engines[at]->ref.ref(); + engines[at]->fontDef = request; +} + +void QFontEngineMultiQWS::draw(QPaintEngine */*p*/, qreal /*x*/, qreal /*y*/, const QTextItemInt &/*si*/) +{ + qFatal("QFontEngineMultiQWS::draw should never be called!"); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_qpf_p.h b/src/gui/text/qfontengine_qpf_p.h new file mode 100644 index 0000000000..571ce1085e --- /dev/null +++ b/src/gui/text/qfontengine_qpf_p.h @@ -0,0 +1,299 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_QPF_P_H +#define QFONTENGINE_QPF_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qfontengine_p.h" +#include +#include + +#ifndef QT_NO_QWS_QPF2 +#if !defined(QT_NO_FREETYPE) +# include "qfontengine_ft_p.h" +#endif +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_QWS_QPF2 + +class QFontEngine; +class QFreetypeFace; + +class Q_GUI_EXPORT QFontEngineQPF : public QFontEngine +{ +public: + // if you add new tags please make sure to update the tables in + // qpfutil.cpp and tools/makeqpf/qpf2.cpp + enum HeaderTag { + Tag_FontName, // 0 string + Tag_FileName, // 1 string + Tag_FileIndex, // 2 quint32 + Tag_FontRevision, // 3 quint32 + Tag_FreeText, // 4 string + Tag_Ascent, // 5 QFixed + Tag_Descent, // 6 QFixed + Tag_Leading, // 7 QFixed + Tag_XHeight, // 8 QFixed + Tag_AverageCharWidth, // 9 QFixed + Tag_MaxCharWidth, // 10 QFixed + Tag_LineThickness, // 11 QFixed + Tag_MinLeftBearing, // 12 QFixed + Tag_MinRightBearing, // 13 QFixed + Tag_UnderlinePosition, // 14 QFixed + Tag_GlyphFormat, // 15 quint8 + Tag_PixelSize, // 16 quint8 + Tag_Weight, // 17 quint8 + Tag_Style, // 18 quint8 + Tag_EndOfHeader, // 19 string + Tag_WritingSystems, // 20 bitfield + + NumTags + }; + + enum TagType { + StringType, + FixedType, + UInt8Type, + UInt32Type, + BitFieldType + }; + + struct Tag + { + quint16 tag; + quint16 size; + }; + + enum GlyphFormat { + BitmapGlyphs = 1, + AlphamapGlyphs = 8 + }; + + enum { + CurrentMajorVersion = 2, + CurrentMinorVersion = 0 + }; + + // The CMap is identical to the TrueType CMap table format + // The GMap table is a normal array with the total number of + // covered glyphs in the TrueType font + enum BlockTag { + CMapBlock, + GMapBlock, + GlyphBlock + }; + + struct Q_PACKED Header + { + char magic[4]; // 'QPF2' + quint32 lock; // values: 0 = unlocked, 0xffffffff = read-only, otherwise qws client id of locking process + quint8 majorVersion; + quint8 minorVersion; + quint16 dataSize; + }; + + struct Q_PACKED Block + { + quint16 tag; + quint16 pad; + quint32 dataSize; + }; + + struct Q_PACKED Glyph + { + quint8 width; + quint8 height; + quint8 bytesPerLine; + qint8 x; + qint8 y; + qint8 advance; + }; + +#ifdef QT_FONTS_ARE_RESOURCES + QFontEngineQPF(const QFontDef &def, const uchar *bytes, int size); +#else + QFontEngineQPF(const QFontDef &def, int fd, QFontEngine *renderingFontEngine = 0); +#endif + ~QFontEngineQPF(); + + FaceId faceId() const { return face_id; } + bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + + bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + void draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &si); + void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags); + QImage alphaMapForGlyph(glyph_t t); + + glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + glyph_metrics_t boundingBox(glyph_t glyph); + + QFixed ascent() const; + QFixed descent() const; + QFixed leading() const; + qreal maxCharWidth() const; + qreal minLeftBearing() const; + qreal minRightBearing() const; + QFixed underlinePosition() const; + QFixed lineThickness() const; + + Type type() const; + + bool canRender(const QChar *string, int len); + inline const char *name() const { return "QPF2"; } + + virtual int glyphCount() const { return glyphMapEntries; } + + bool isValid() const; + + const Glyph *findGlyph(glyph_t g) const; + + static bool verifyHeader(const uchar *data, int size); + static QVariant extractHeaderField(const uchar *data, HeaderTag tag); + static QList cleanUpAfterClientCrash(const QList &crashedClientIds); + +#if !defined(QT_NO_FREETYPE) + FT_Face lockFace() const; + void unlockFace() const; + void doKerning(QGlyphLayout *g, QTextEngine::ShaperFlags flags) const; + virtual HB_Error getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints); + virtual QFixed emSquareSize() const; +#endif + + inline QString fontFile() const { return fileName; } + + QFontEngine *renderingEngine() const { return renderingFontEngine; } + + QFontEngine *takeRenderingEngine() + { + QFontEngine *engine = renderingFontEngine; + renderingFontEngine = 0; + return engine; + } + +private: +#if !defined(QT_NO_FREETYPE) + void ensureGlyphsLoaded(const QGlyphLayout &glyphs); + void loadGlyph(glyph_t glyph); + bool lockFile(); + void unlockFile(); + void remapFontData(); +#endif + + int fd; + const uchar *fontData; + int dataSize; + const uchar *externalCMap; + quint32 cmapOffset; + int cmapSize; + quint32 glyphMapOffset; + quint32 glyphMapEntries; + quint32 glyphDataOffset; + quint32 glyphDataSize; + QString fileName; + QByteArray encodedFileName; + bool readOnly; + + QFreetypeFace *freetype; + FaceId face_id; + QByteArray freetypeCMapTable; + mutable bool kerning_pairs_loaded; + QFontEngine *renderingFontEngine; +}; + +struct QPFGenerator +{ + QPFGenerator(QBuffer *device, QFontEngine *engine) + : dev(device), fe(engine) {} + + void generate(); + void writeHeader(); + void writeGMap(); + void writeBlock(QFontEngineQPF::BlockTag tag, const QByteArray &data); + + void writeTaggedString(QFontEngineQPF::HeaderTag tag, const QByteArray &string); + void writeTaggedUInt32(QFontEngineQPF::HeaderTag tag, quint32 value); + void writeTaggedUInt8(QFontEngineQPF::HeaderTag tag, quint8 value); + void writeTaggedQFixed(QFontEngineQPF::HeaderTag tag, QFixed value); + + void writeUInt16(quint16 value) { value = qToBigEndian(value); dev->write((const char *)&value, sizeof(value)); } + void writeUInt32(quint32 value) { value = qToBigEndian(value); dev->write((const char *)&value, sizeof(value)); } + void writeUInt8(quint8 value) { dev->write((const char *)&value, sizeof(value)); } + void writeInt8(qint8 value) { dev->write((const char *)&value, sizeof(value)); } + + void align4() { while (dev->pos() & 3) { dev->putChar('\0'); } } + + QBuffer *dev; + QFontEngine *fe; +}; + +#endif // QT_NO_QWS_QPF2 + +class QFontEngineMultiQWS : public QFontEngineMulti +{ +public: + QFontEngineMultiQWS(QFontEngine *fe, int script, const QStringList &fallbacks); + + void loadEngine(int at); + void draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &si); + +private: + QStringList fallbackFamilies; + int script; +}; + +QT_END_NAMESPACE + +#endif // QFONTENGINE_QPF_P_H diff --git a/src/gui/text/qfontengine_qws.cpp b/src/gui/text/qfontengine_qws.cpp new file mode 100644 index 0000000000..b71c4a7aba --- /dev/null +++ b/src/gui/text/qfontengine_qws.cpp @@ -0,0 +1,665 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontengine_p.h" +#include +#include +#include +#include +#include +#include +#include "qtextengine_p.h" +#include "private/qcore_unix_p.h" // overrides QT_OPEN + +#include + + +#ifndef QT_NO_QWS_QPF + +#include "qfile.h" +#include "qdir.h" + +#define QT_USE_MMAP +#include + +#ifdef QT_USE_MMAP +// for mmap +#include +#include +#include +#include +#include +#include + +# if defined(QT_LINUXBASE) && !defined(MAP_FILE) + // LSB 3.2 does not define MAP_FILE +# define MAP_FILE 0 +# endif + +#endif + +#endif // QT_NO_QWS_QPF + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_QWS_QPF +QT_BEGIN_INCLUDE_NAMESPACE +#include "qplatformdefs.h" +QT_END_INCLUDE_NAMESPACE + +static inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +#define FM_SMOOTH 1 + + +class Q_PACKED QPFGlyphMetrics { + +public: + quint8 linestep; + quint8 width; + quint8 height; + quint8 flags; + + qint8 bearingx; // Difference from pen position to glyph's left bbox + quint8 advance; // Difference between pen positions + qint8 bearingy; // Used for putting characters on baseline + + qint8 reserved; // Do not use + + // Flags: + // RendererOwnsData - the renderer is responsible for glyph data + // memory deletion otherwise QPFGlyphTree must + // delete [] the data when the glyph is deleted. + enum Flags { RendererOwnsData=0x01 }; +}; + +class QPFGlyph { +public: + QPFGlyph() { metrics=0; data=0; } + QPFGlyph(QPFGlyphMetrics* m, uchar* d) : + metrics(m), data(d) { } + ~QPFGlyph() {} + + QPFGlyphMetrics* metrics; + uchar* data; +}; + +struct Q_PACKED QPFFontMetrics{ + qint8 ascent,descent; + qint8 leftbearing,rightbearing; + quint8 maxwidth; + qint8 leading; + quint8 flags; + quint8 underlinepos; + quint8 underlinewidth; + quint8 reserved3; +}; + + +class QPFGlyphTree { +public: + /* reads in a tree like this: + + A-Z + / \ + 0-9 a-z + + etc. + + */ + glyph_t min,max; + QPFGlyphTree* less; + QPFGlyphTree* more; + QPFGlyph* glyph; +public: +#ifdef QT_USE_MMAP + QPFGlyphTree(uchar*& data) + { + read(data); + } +#else + QPFGlyphTree(QIODevice& f) + { + read(f); + } +#endif + + ~QPFGlyphTree() + { + // NOTE: does not delete glyph[*].metrics or .data. + // the caller does this (only they know who owns + // the data). See clear(). + delete less; + delete more; + delete [] glyph; + } + + bool inFont(glyph_t g) const + { + if ( g < min ) { + if ( !less ) + return false; + return less->inFont(g); + } else if ( g > max ) { + if ( !more ) + return false; + return more->inFont(g); + } + return true; + } + + QPFGlyph* get(glyph_t g) + { + if ( g < min ) { + if ( !less ) + return 0; + return less->get(g); + } else if ( g > max ) { + if ( !more ) + return 0; + return more->get(g); + } + return &glyph[g - min]; + } + int totalChars() const + { + if ( !this ) return 0; + return max-min+1 + less->totalChars() + more->totalChars(); + } + int weight() const + { + if ( !this ) return 0; + return 1 + less->weight() + more->weight(); + } + + void dump(int indent=0) + { + for (int i=0; idump(indent+1); + if ( more ) more->dump(indent+1); + } + +private: + QPFGlyphTree() + { + } + +#ifdef QT_USE_MMAP + void read(uchar*& data) + { + // All node data first + readNode(data); + // Then all non-video data + readMetrics(data); + // Then all video data + readData(data); + } +#else + void read(QIODevice& f) + { + // All node data first + readNode(f); + // Then all non-video data + readMetrics(f); + // Then all video data + readData(f); + } +#endif + +#ifdef QT_USE_MMAP + void readNode(uchar*& data) + { + uchar rw = *data++; + uchar cl = *data++; + min = (rw << 8) | cl; + rw = *data++; + cl = *data++; + max = (rw << 8) | cl; + int flags = *data++; + if ( flags & 1 ) + less = new QPFGlyphTree; + else + less = 0; + if ( flags & 2 ) + more = new QPFGlyphTree; + else + more = 0; + int n = max-min+1; + glyph = new QPFGlyph[n]; + + if ( less ) + less->readNode(data); + if ( more ) + more->readNode(data); + } +#else + void readNode(QIODevice& f) + { + char rw; + char cl; + f.getChar(&rw); + f.getChar(&cl); + min = (rw << 8) | cl; + f.getChar(&rw); + f.getChar(&cl); + max = (rw << 8) | cl; + char flags; + f.getChar(&flags); + if ( flags & 1 ) + less = new QPFGlyphTree; + else + less = 0; + if ( flags & 2 ) + more = new QPFGlyphTree; + else + more = 0; + int n = max-min+1; + glyph = new QPFGlyph[n]; + + if ( less ) + less->readNode(f); + if ( more ) + more->readNode(f); + } +#endif + +#ifdef QT_USE_MMAP + void readMetrics(uchar*& data) + { + int n = max-min+1; + for (int i=0; ireadMetrics(data); + if ( more ) + more->readMetrics(data); + } +#else + void readMetrics(QIODevice& f) + { + int n = max-min+1; + for (int i=0; ireadMetrics(f); + if ( more ) + more->readMetrics(f); + } +#endif + +#ifdef QT_USE_MMAP + void readData(uchar*& data) + { + int n = max-min+1; + for (int i=0; iwidth, glyph[i].metrics->height ); + //######### s = qt_screen->mapToDevice( s ); + uint datasize = glyph[i].metrics->linestep * s.height(); + glyph[i].data = data; data += datasize; + } + if ( less ) + less->readData(data); + if ( more ) + more->readData(data); + } +#else + void readData(QIODevice& f) + { + int n = max-min+1; + for (int i=0; iwidth, glyph[i].metrics->height ); + //############### s = qt_screen->mapToDevice( s ); + uint datasize = glyph[i].metrics->linestep * s.height(); + glyph[i].data = new uchar[datasize]; // ### deleted? + f.read((char*)glyph[i].data, datasize); + } + if ( less ) + less->readData(f); + if ( more ) + more->readData(f); + } +#endif + +}; + +class QFontEngineQPF1Data +{ +public: + QPFFontMetrics fm; + QPFGlyphTree *tree; + void *mmapStart; + size_t mmapLength; +}; + +#if defined(Q_OS_INTEGRITY) +static void *qt_mmap(void *start, size_t length, int /*prot*/, int /*flags*/, int fd, off_t offset) +{ + // INTEGRITY cannot mmap local files - load it into a local buffer + if (::lseek(fd, offset, SEEK_SET) == -1) { +# if defined(DEBUG_FONTENGINE) + perror("lseek failed"); +# endif + } + void *buf = malloc(length); + if (::read(fd, buf, length) != (ssize_t)length) { +# if defined(DEBUG_FONTENGINE) + perror("read failed"); +# endif + } + + return buf; +} +#else +static inline void *qt_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) +{ + return mmap(start, length, prot, flags, fd, offset); +} +#endif + + + +QFontEngineQPF1::QFontEngineQPF1(const QFontDef&, const QString &fn) +{ + cache_cost = 1; + + int f = QT_OPEN( QFile::encodeName(fn), O_RDONLY, 0); + Q_ASSERT(f>=0); + QT_STATBUF st; + if ( QT_FSTAT( f, &st ) ) + qFatal("Failed to stat %s",QFile::encodeName(fn).data()); + uchar* data = (uchar*)qt_mmap( 0, // any address + st.st_size, // whole file + PROT_READ, // read-only memory +#if defined(Q_OS_INTEGRITY) + 0, +#elif !defined(Q_OS_SOLARIS) && !defined(Q_OS_QNX4) && !defined(Q_OS_VXWORKS) + MAP_FILE | MAP_PRIVATE, // swap-backed map from file +#else + MAP_PRIVATE, +#endif + f, 0 ); // from offset 0 of f +#if !defined(MAP_FAILED) && (defined(Q_OS_QNX4) || defined(Q_OS_INTEGRITY)) +#define MAP_FAILED ((void *)-1) +#endif + if ( !data || data == (uchar*)MAP_FAILED ) + qFatal("Failed to mmap %s",QFile::encodeName(fn).data()); + QT_CLOSE(f); + + d = new QFontEngineQPF1Data; + d->mmapStart = data; + d->mmapLength = st.st_size; + memcpy(reinterpret_cast(&d->fm),data,sizeof(d->fm)); + + data += sizeof(d->fm); + d->tree = new QPFGlyphTree(data); + glyphFormat = (d->fm.flags & FM_SMOOTH) ? QFontEngineGlyphCache::Raster_A8 + : QFontEngineGlyphCache::Raster_Mono; +#if 0 + qDebug() << "font file" << fn + << "ascent" << d->fm.ascent << "descent" << d->fm.descent + << "leftbearing" << d->fm.leftbearing + << "rightbearing" << d->fm.rightbearing + << "maxwidth" << d->fm.maxwidth + << "leading" << d->fm.leading + << "flags" << d->fm.flags + << "underlinepos" << d->fm.underlinepos + << "underlinewidth" << d->fm.underlinewidth; +#endif +} + +QFontEngineQPF1::~QFontEngineQPF1() +{ + if (d->mmapStart) + munmap(d->mmapStart, d->mmapLength); + delete d->tree; + delete d; +} + + +bool QFontEngineQPF1::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if(*nglyphs < len) { + *nglyphs = len; + return false; + } + *nglyphs = 0; + + bool mirrored = flags & QTextEngine::RightToLeft; + for(int i = 0; i < len; i++) { + unsigned int uc = getChar(str, i, len); + if (mirrored) + uc = QChar::mirroredChar(uc); + glyphs->glyphs[*nglyphs] = uc < 0x10000 ? uc : 0; + ++*nglyphs; + } + + glyphs->numGlyphs = *nglyphs; + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + recalcAdvances(glyphs, flags); + + return true; +} + +void QFontEngineQPF1::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const +{ + for(int i = 0; i < glyphs->numGlyphs; i++) { + QPFGlyph *glyph = d->tree->get(glyphs->glyphs[i]); + + glyphs->advances_x[i] = glyph ? glyph->metrics->advance : 0; + glyphs->advances_y[i] = 0; + + if (!glyph) + glyphs->glyphs[i] = 0; + } +} + +void QFontEngineQPF1::draw(QPaintEngine *p, qreal _x, qreal _y, const QTextItemInt &si) +{ + QPaintEngineState *pState = p->state; + QRasterPaintEngine *paintEngine = static_cast(p); + + QTransform matrix = pState->transform(); + matrix.translate(_x, _y); + QFixed x = QFixed::fromReal(matrix.dx()); + QFixed y = QFixed::fromReal(matrix.dy()); + + QVarLengthArray positions; + QVarLengthArray glyphs; + getGlyphPositions(si.glyphs, matrix, si.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + int depth = (d->fm.flags & FM_SMOOTH) ? 8 : 1; + for(int i = 0; i < glyphs.size(); i++) { + const QPFGlyph *glyph = d->tree->get(glyphs[i]); + if (!glyph) + continue; + + int bpl = glyph->metrics->linestep; + + if(glyph->data) + paintEngine->alphaPenBlt(glyph->data, bpl, depth, + qRound(positions[i].x) + glyph->metrics->bearingx, + qRound(positions[i].y) - glyph->metrics->bearingy, + glyph->metrics->width,glyph->metrics->height); + } +} + + +QImage QFontEngineQPF1::alphaMapForGlyph(glyph_t g) +{ + const QPFGlyph *glyph = d->tree->get(g); + if (!glyph) + return QImage(); + + int mono = !(d->fm.flags & FM_SMOOTH); + + const uchar *bits = glyph->data;//((const uchar *) glyph); + + QImage image; + if (mono) { + image = QImage((glyph->metrics->width+7)&~7, glyph->metrics->height, QImage::Format_Mono); + image.setColor(0, qRgba(0, 0, 0, 0)); + image.setColor(1, qRgba(0, 0, 0, 255)); + } else { + image = QImage(glyph->metrics->width, glyph->metrics->height, QImage::Format_Indexed8); + for (int j=0; j<256; ++j) + image.setColor(j, qRgba(0, 0, 0, j)); + } + for (int i=0; imetrics->height; ++i) { + memcpy(image.scanLine(i), bits, glyph->metrics->linestep); + bits += glyph->metrics->linestep; + } + return image; +} + + + +void QFontEngineQPF1::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + addBitmapFontToPath(x, y, glyphs, path, flags); +} + +glyph_metrics_t QFontEngineQPF1::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs == 0) + return glyph_metrics_t(); + + QFixed w = 0; + for (int i = 0; i < glyphs.numGlyphs; ++i) + w += glyphs.effectiveAdvance(i); + return glyph_metrics_t(0, -ascent(), w - lastRightBearing(glyphs), ascent()+descent()+1, w, 0); +} + +glyph_metrics_t QFontEngineQPF1::boundingBox(glyph_t glyph) +{ + const QPFGlyph *g = d->tree->get(glyph); + if (!g) + return glyph_metrics_t(); + Q_ASSERT(g); + return glyph_metrics_t(g->metrics->bearingx, -g->metrics->bearingy, + g->metrics->width, g->metrics->height, + g->metrics->advance, 0); +} + +QFixed QFontEngineQPF1::ascent() const +{ + return d->fm.ascent; +} + +QFixed QFontEngineQPF1::descent() const +{ + return d->fm.descent; +} + +QFixed QFontEngineQPF1::leading() const +{ + return d->fm.leading; +} + +qreal QFontEngineQPF1::maxCharWidth() const +{ + return d->fm.maxwidth; +} +/* +const char *QFontEngineQPF1::name() const +{ + return "qt"; +} +*/ +bool QFontEngineQPF1::canRender(const QChar *str, int len) +{ + for(int i = 0; i < len; i++) + if (!d->tree->inFont(str[i].unicode())) + return false; + return true; +} + +QFontEngine::Type QFontEngineQPF1::type() const +{ + return QPF1; +} + +qreal QFontEngineQPF1::minLeftBearing() const +{ + return d->fm.leftbearing; +} + +qreal QFontEngineQPF1::minRightBearing() const +{ + return d->fm.rightbearing; +} + +QFixed QFontEngineQPF1::underlinePosition() const +{ + return d->fm.underlinepos; +} + +QFixed QFontEngineQPF1::lineThickness() const +{ + return d->fm.underlinewidth; +} + +#endif //QT_NO_QWS_QPF + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_s60.cpp b/src/gui/text/qfontengine_s60.cpp new file mode 100644 index 0000000000..e9b54e350f --- /dev/null +++ b/src/gui/text/qfontengine_s60.cpp @@ -0,0 +1,569 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfontengine_s60_p.h" +#include "qtextengine_p.h" +#include "qendian.h" +#include "qglobal.h" +#include +#include "qimage.h" +#include +#include + +#include +#include +#include +#include +#if defined(Q_SYMBIAN_HAS_GLYPHOUTLINE_API) +#include +#endif // Q_SYMBIAN_HAS_GLYPHOUTLINE_API + +// Replication of TGetFontTableParam & friends. +// There is unfortunately no compile time flag like SYMBIAN_FONT_TABLE_API +// that would help us to only replicate these things for Symbian versions +// that do not yet have the font table Api. Symbian's public SDK does +// generally not define any usable macros. +class QSymbianTGetFontTableParam +{ +public: + TUint32 iTag; + TAny *iContent; + TInt iLength; +}; +const TUid QSymbianKFontGetFontTable = {0x102872C1}; +const TUid QSymbianKFontReleaseFontTable = {0x2002AC24}; + +QT_BEGIN_NAMESPACE + +QSymbianTypeFaceExtras::QSymbianTypeFaceExtras(CFont* cFont, COpenFont *openFont) + : m_cFont(cFont) + , m_symbolCMap(false) + , m_openFont(openFont) +{ + if (!symbianFontTableApiAvailable()) { + TAny *trueTypeExtension = NULL; + m_openFont->ExtendedInterface(KUidOpenFontTrueTypeExtension, trueTypeExtension); + m_trueTypeExtension = static_cast(trueTypeExtension); + Q_ASSERT(m_trueTypeExtension); + } +} + +QSymbianTypeFaceExtras::~QSymbianTypeFaceExtras() +{ + if (symbianFontTableApiAvailable()) + S60->screenDevice()->ReleaseFont(m_cFont); +} + +QByteArray QSymbianTypeFaceExtras::getSfntTable(uint tag) const +{ + if (symbianFontTableApiAvailable()) { + QSymbianTGetFontTableParam fontTableParams = { tag, 0, 0 }; + if (m_cFont->ExtendedFunction(QSymbianKFontGetFontTable, &fontTableParams) == KErrNone) { + const char* const fontTableContent = + static_cast(fontTableParams.iContent); + const QByteArray fontTable(fontTableContent, fontTableParams.iLength); + m_cFont->ExtendedFunction(QSymbianKFontReleaseFontTable, &fontTableParams); + return fontTable; + } + return QByteArray(); + } else { + Q_ASSERT(m_trueTypeExtension->HasTrueTypeTable(tag)); + TInt error = KErrNone; + TInt tableByteLength = 0; + TAny *table = m_trueTypeExtension->GetTrueTypeTable(error, tag, &tableByteLength); + Q_CHECK_PTR(table); + const QByteArray result(static_cast(table), tableByteLength); + m_trueTypeExtension->ReleaseTrueTypeTable(table); + return result; + } +} + +bool QSymbianTypeFaceExtras::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + bool result = true; + if (symbianFontTableApiAvailable()) { + QSymbianTGetFontTableParam fontTableParams = { tag, 0, 0 }; + if (m_cFont->ExtendedFunction(QSymbianKFontGetFontTable, &fontTableParams) == KErrNone) { + if (*length > 0 && *length < fontTableParams.iLength) { + result = false; // Caller did not allocate enough memory + } else { + *length = fontTableParams.iLength; + if (buffer) + memcpy(buffer, fontTableParams.iContent, fontTableParams.iLength); + } + m_cFont->ExtendedFunction(QSymbianKFontReleaseFontTable, &fontTableParams); + } else { + result = false; + } + } else { + if (!m_trueTypeExtension->HasTrueTypeTable(tag)) + return false; + + TInt error = KErrNone; + TInt tableByteLength; + TAny *table = m_trueTypeExtension->GetTrueTypeTable(error, tag, &tableByteLength); + Q_CHECK_PTR(table); + + if (error != KErrNone) { + return false; + } else if (*length > 0 && *length < tableByteLength) { + result = false; // Caller did not allocate enough memory + } else { + *length = tableByteLength; + if (buffer) + memcpy(buffer, table, tableByteLength); + } + + m_trueTypeExtension->ReleaseTrueTypeTable(table); + } + return result; +} + +const uchar *QSymbianTypeFaceExtras::cmap() const +{ + if (m_cmapTable.isNull()) { + const QByteArray cmapTable = getSfntTable(MAKE_TAG('c', 'm', 'a', 'p')); + int size = 0; + const uchar *cmap = QFontEngine::getCMap(reinterpret_cast + (cmapTable.constData()), cmapTable.size(), &m_symbolCMap, &size); + m_cmapTable = QByteArray(reinterpret_cast(cmap), size); + } + return reinterpret_cast(m_cmapTable.constData()); +} + +bool QSymbianTypeFaceExtras::isSymbolCMap() const +{ + return m_symbolCMap; +} + +CFont *QSymbianTypeFaceExtras::fontOwner() const +{ + return m_cFont; +} + +QFixed QSymbianTypeFaceExtras::unitsPerEm() const +{ + if (m_unitsPerEm.value() != 0) + return m_unitsPerEm; + const QByteArray head = getSfntTable(MAKE_TAG('h', 'e', 'a', 'd')); + const int unitsPerEmOffset = 18; + if (head.size() > unitsPerEmOffset + sizeof(quint16)) { + const uchar* tableData = reinterpret_cast(head.constData()); + const uchar* unitsPerEm = tableData + unitsPerEmOffset; + m_unitsPerEm = qFromBigEndian(unitsPerEm); + } else { + // Bitmap font? Corrupt font? + // We return -1 and let the QFontEngineS60 return the pixel size. + m_unitsPerEm = -1; + } + return m_unitsPerEm; +} + +bool QSymbianTypeFaceExtras::symbianFontTableApiAvailable() +{ + enum FontTableApiAvailability { + Unknown, + Available, + Unavailable + }; + static FontTableApiAvailability availability = + QSysInfo::symbianVersion() < QSysInfo::SV_SF_3 ? + Unavailable : Unknown; + if (availability == Unknown) { + // Actually, we should ask CFeatureDiscovery::IsFeatureSupportedL() + // with FfFontTable here. But since at the time of writing, the + // FfFontTable flag check either gave false positives or false + // negatives. Here comes an implicit check via CFont::ExtendedFunction. + QSymbianTGetFontTableParam fontTableParams = { + MAKE_TAG('O', 'S', '/', '2'), 0, 0 }; + QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock); + CFont *font; + const TInt getFontErr = S60->screenDevice()->GetNearestFontInTwips(font, TFontSpec()); + Q_ASSERT(getFontErr == KErrNone); + if (font->ExtendedFunction(QSymbianKFontGetFontTable, &fontTableParams) == KErrNone) { + font->ExtendedFunction(QSymbianKFontReleaseFontTable, &fontTableParams); + availability = Available; + } else { + availability = Unavailable; + } + S60->screenDevice()->ReleaseFont(font); + lock.relock(); + } + return availability == Available; +} + +// duplicated from qfontengine_xyz.cpp +static inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +extern QString qt_symbian_fontNameWithAppFontMarker(const QString &fontName); // qfontdatabase_s60.cpp + +CFont *QFontEngineS60::fontWithSize(qreal size) const +{ + CFont *result = 0; + const QString family = qt_symbian_fontNameWithAppFontMarker(QFontEngine::fontDef.family); + TFontSpec fontSpec(qt_QString2TPtrC(family), TInt(size)); + fontSpec.iFontStyle.SetBitmapType(EAntiAliasedGlyphBitmap); + fontSpec.iFontStyle.SetPosture(QFontEngine::fontDef.style == QFont::StyleNormal?EPostureUpright:EPostureItalic); + fontSpec.iFontStyle.SetStrokeWeight(QFontEngine::fontDef.weight > QFont::Normal?EStrokeWeightBold:EStrokeWeightNormal); + const TInt errorCode = S60->screenDevice()->GetNearestFontToDesignHeightInPixels(result, fontSpec); + Q_ASSERT(result && (errorCode == 0)); + return result; +} + +void QFontEngineS60::setFontScale(qreal scale) +{ + if (qFuzzyCompare(scale, qreal(1))) { + if (!m_originalFont) + m_originalFont = fontWithSize(m_originalFontSizeInPixels); + m_activeFont = m_originalFont; + } else { + const qreal scaledFontSizeInPixels = m_originalFontSizeInPixels * scale; + if (!m_scaledFont || + (TInt(scaledFontSizeInPixels) != TInt(m_scaledFontSizeInPixels))) { + releaseFont(m_scaledFont); + m_scaledFontSizeInPixels = scaledFontSizeInPixels; + m_scaledFont = fontWithSize(m_scaledFontSizeInPixels); + } + m_activeFont = m_scaledFont; + } +} + +void QFontEngineS60::releaseFont(CFont *&font) +{ + if (font) { + S60->screenDevice()->ReleaseFont(font); + font = 0; + } +} + +QFontEngineS60::QFontEngineS60(const QFontDef &request, const QSymbianTypeFaceExtras *extras) + : m_extras(extras) + , m_originalFont(0) + , m_originalFontSizeInPixels((request.pixelSize >= 0)? + request.pixelSize:pointsToPixels(request.pointSize)) + , m_scaledFont(0) + , m_scaledFontSizeInPixels(0) + , m_activeFont(0) +{ + QFontEngine::fontDef = request; + setFontScale(1.0); + cache_cost = sizeof(QFontEngineS60); +} + +QFontEngineS60::~QFontEngineS60() +{ + releaseFont(m_originalFont); + releaseFont(m_scaledFont); +} + +QFixed QFontEngineS60::emSquareSize() const +{ + const QFixed unitsPerEm = m_extras->unitsPerEm(); + return unitsPerEm.toInt() == -1 ? + QFixed::fromReal(m_originalFontSizeInPixels) : unitsPerEm; +} + +bool QFontEngineS60::stringToCMap(const QChar *characters, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + + HB_Glyph *g = glyphs->glyphs; + const unsigned char* cmap = m_extras->cmap(); + const bool isRtl = (flags & QTextEngine::RightToLeft); + for (int i = 0; i < len; ++i) { + const unsigned int uc = getChar(characters, i, len); + *g++ = QFontEngine::getTrueTypeGlyphIndex(cmap, + (isRtl && !m_extras->isSymbolCMap()) ? QChar::mirroredChar(uc) : uc); + } + + glyphs->numGlyphs = g - glyphs->glyphs; + *nglyphs = glyphs->numGlyphs; + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + recalcAdvances(glyphs, flags); + return true; +} + +void QFontEngineS60::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + Q_UNUSED(flags); + TOpenFontCharMetrics metrics; + const TUint8 *glyphBitmapBytes; + TSize glyphBitmapSize; + for (int i = 0; i < glyphs->numGlyphs; i++) { + getCharacterData(glyphs->glyphs[i], metrics, glyphBitmapBytes, glyphBitmapSize); + glyphs->advances_x[i] = metrics.HorizAdvance(); + glyphs->advances_y[i] = 0; + } +} + +#ifdef Q_SYMBIAN_HAS_GLYPHOUTLINE_API +static bool parseGlyphPathData(const char *dataStr, const char *dataEnd, QPainterPath &path, + qreal fontPixelSize, const QPointF &offset, bool hinted); +#endif //Q_SYMBIAN_HAS_GLYPHOUTLINE_API + +void QFontEngineS60::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, + int nglyphs, QPainterPath *path, + QTextItem::RenderFlags flags) +{ +#ifdef Q_SYMBIAN_HAS_GLYPHOUTLINE_API + Q_UNUSED(flags) + RGlyphOutlineIterator iterator; + const TInt error = iterator.Open(*m_activeFont, glyphs, nglyphs); + if (KErrNone != error) + return; + const qreal fontSizeInPixels = qreal(m_activeFont->HeightInPixels()); + int count = 0; + do { + const TUint8* outlineUint8 = iterator.Outline(); + const char* const outlineChar = reinterpret_cast(outlineUint8); + const char* const outlineEnd = outlineChar + iterator.OutlineLength(); + parseGlyphPathData(outlineChar, outlineEnd, *path, fontSizeInPixels, + positions[count++].toPointF(), false); + } while(KErrNone == iterator.Next() && count <= nglyphs); + iterator.Close(); +#else // Q_SYMBIAN_HAS_GLYPHOUTLINE_API + QFontEngine::addGlyphsToPath(glyphs, positions, nglyphs, path, flags); +#endif //Q_SYMBIAN_HAS_GLYPHOUTLINE_API +} + +QImage QFontEngineS60::alphaMapForGlyph(glyph_t glyph) +{ + // Note: On some Symbian versions (apparently <= Symbian^1), this + // function will return gray values 0x00, 0x10 ... 0xe0, 0xf0 due + // to a bug. The glyphs are nowhere perfectly opaque. + // This has been fixed for Symbian^3. + + TOpenFontCharMetrics metrics; + const TUint8 *glyphBitmapBytes; + TSize glyphBitmapSize; + getCharacterData(glyph, metrics, glyphBitmapBytes, glyphBitmapSize); + QImage result(glyphBitmapBytes, glyphBitmapSize.iWidth, glyphBitmapSize.iHeight, glyphBitmapSize.iWidth, QImage::Format_Indexed8); + result.setColorTable(grayPalette()); + return result; +} + +glyph_metrics_t QFontEngineS60::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs == 0) + return glyph_metrics_t(); + + QFixed w = 0; + for (int i = 0; i < glyphs.numGlyphs; ++i) + w += glyphs.effectiveAdvance(i); + + return glyph_metrics_t(0, -ascent(), w - lastRightBearing(glyphs), ascent()+descent()+1, w, 0); +} + +glyph_metrics_t QFontEngineS60::boundingBox_const(glyph_t glyph) const +{ + TOpenFontCharMetrics metrics; + const TUint8 *glyphBitmapBytes; + TSize glyphBitmapSize; + getCharacterData(glyph, metrics, glyphBitmapBytes, glyphBitmapSize); + const glyph_metrics_t result( + metrics.HorizBearingX(), + -metrics.HorizBearingY(), + metrics.Width(), + metrics.Height(), + metrics.HorizAdvance(), + 0 + ); + return result; +} + +glyph_metrics_t QFontEngineS60::boundingBox(glyph_t glyph) +{ + return boundingBox_const(glyph); +} + +QFixed QFontEngineS60::ascent() const +{ + // Workaround for QTBUG-8013 + // Stroke based fonts may return an incorrect FontMaxAscent of 0. + const QFixed ascent = m_originalFont->FontMaxAscent(); + return (ascent > 0) ? ascent : QFixed::fromReal(m_originalFontSizeInPixels) - descent(); +} + +QFixed QFontEngineS60::descent() const +{ + return m_originalFont->FontMaxDescent(); +} + +QFixed QFontEngineS60::leading() const +{ + return 0; +} + +qreal QFontEngineS60::maxCharWidth() const +{ + return m_originalFont->MaxCharWidthInPixels(); +} + +const char *QFontEngineS60::name() const +{ + return "QFontEngineS60"; +} + +bool QFontEngineS60::canRender(const QChar *string, int len) +{ + const unsigned char *cmap = m_extras->cmap(); + for (int i = 0; i < len; ++i) { + const unsigned int uc = getChar(string, i, len); + if (QFontEngine::getTrueTypeGlyphIndex(cmap, uc) == 0) + return false; + } + return true; +} + +QByteArray QFontEngineS60::getSfntTable(uint tag) const +{ + return m_extras->getSfntTable(tag); +} + +bool QFontEngineS60::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + return m_extras->getSfntTableData(tag, buffer, length); +} + +QFontEngine::Type QFontEngineS60::type() const +{ + return QFontEngine::S60FontEngine; +} + +void QFontEngineS60::getCharacterData(glyph_t glyph, TOpenFontCharMetrics& metrics, const TUint8*& bitmap, TSize& bitmapSize) const +{ + // Setting the most significant bit tells GetCharacterData + // that 'code' is a Glyph ID, rather than a UTF-16 value + const TUint specialCode = (TUint)glyph | 0x80000000; + + const CFont::TCharacterDataAvailability availability = + m_activeFont->GetCharacterData(specialCode, metrics, bitmap, bitmapSize); + const glyph_t fallbackGlyph = '?'; + if (availability != CFont::EAllCharacterData) { + const CFont::TCharacterDataAvailability fallbackAvailability = + m_activeFont->GetCharacterData(fallbackGlyph, metrics, bitmap, bitmapSize); + Q_ASSERT(fallbackAvailability == CFont::EAllCharacterData); + } +} + +#ifdef Q_SYMBIAN_HAS_GLYPHOUTLINE_API +static inline void skipSpacesAndComma(const char* &str, const char* const strEnd) +{ + while (str <= strEnd && (*str == ' ' || *str == ',')) + ++str; +} + +static bool parseGlyphPathData(const char *svgPath, const char *svgPathEnd, QPainterPath &path, + qreal fontPixelSize, const QPointF &offset, bool hinted) +{ + Q_UNUSED(hinted) + QPointF p1, p2, firstSubPathPoint; + qreal *elementValues[] = + {&p1.rx(), &p1.ry(), &p2.rx(), &p2.ry()}; + const int unitsPerEm = 2048; // See: http://en.wikipedia.org/wiki/Em_%28typography%29 + const qreal resizeFactor = fontPixelSize / unitsPerEm; + + while (svgPath < svgPathEnd) { + skipSpacesAndComma(svgPath, svgPathEnd); + const char pathElem = *svgPath++; + skipSpacesAndComma(svgPath, svgPathEnd); + + if (pathElem != 'Z') { + char *endStr = 0; + int elementValuesCount = 0; + for (int i = 0; i < 4; ++i) { // 4 = size of elementValues[] + qreal coordinateValue = strtod(svgPath, &endStr); + if (svgPath == endStr) + break; + if (i % 2) // Flip vertically + coordinateValue = -coordinateValue; + *elementValues[i] = coordinateValue * resizeFactor; + elementValuesCount++; + svgPath = endStr; + skipSpacesAndComma(svgPath, svgPathEnd); + } + p1 += offset; + if (elementValuesCount == 2) + p2 = firstSubPathPoint; + else + p2 += offset; + } + + switch (pathElem) { + case 'M': + firstSubPathPoint = p1; + path.moveTo(p1); + break; + case 'Z': + path.closeSubpath(); + break; + case 'L': + path.lineTo(p1); + break; + case 'Q': + path.quadTo(p1, p2); + break; + default: + return false; + } + } + return true; +} +#endif // Q_SYMBIAN_HAS_GLYPHOUTLINE_API + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_s60_p.h b/src/gui/text/qfontengine_s60_p.h new file mode 100644 index 0000000000..c0eeef5264 --- /dev/null +++ b/src/gui/text/qfontengine_s60_p.h @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_S60_P_H +#define QFONTENGINE_S60_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qconfig.h" +#include +#include "qsize.h" +#include + +// The glyph outline code is intentionally disabled. It will be reactivated as +// soon as the glyph outline API is backported from Symbian(^4) to Symbian(^3). +#if 0 +#define Q_SYMBIAN_HAS_GLYPHOUTLINE_API +#endif + +class CFont; + +QT_BEGIN_NAMESPACE + +// ..gives us access to truetype tables +class QSymbianTypeFaceExtras +{ +public: + QSymbianTypeFaceExtras(CFont* cFont, COpenFont *openFont = 0); + ~QSymbianTypeFaceExtras(); + + QByteArray getSfntTable(uint tag) const; + bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + const uchar *cmap() const; + CFont *fontOwner() const; + bool isSymbolCMap() const; + QFixed unitsPerEm() const; + static bool symbianFontTableApiAvailable(); + +private: + CFont* m_cFont; + mutable bool m_symbolCMap; + mutable QByteArray m_cmapTable; + mutable QFixed m_unitsPerEm; + + // m_openFont and m_openFont are used if Symbian does not provide + // the Font Table API + COpenFont *m_openFont; + mutable MOpenFontTrueTypeExtension *m_trueTypeExtension; +}; + +class QFontEngineS60 : public QFontEngine +{ +public: + QFontEngineS60(const QFontDef &fontDef, const QSymbianTypeFaceExtras *extras); + ~QFontEngineS60(); + + QFixed emSquareSize() const; + bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + void recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const; + + void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, + QPainterPath *path, QTextItem::RenderFlags flags); + + QImage alphaMapForGlyph(glyph_t glyph); + + glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + glyph_metrics_t boundingBox_const(glyph_t glyph) const; // Const correctnes quirk. + glyph_metrics_t boundingBox(glyph_t glyph); + + QFixed ascent() const; + QFixed descent() const; + QFixed leading() const; + qreal maxCharWidth() const; + qreal minLeftBearing() const { return 0; } + qreal minRightBearing() const { return 0; } + + QByteArray getSfntTable(uint tag) const; + bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + + static qreal pixelsToPoints(qreal pixels, Qt::Orientation orientation = Qt::Horizontal); + static qreal pointsToPixels(qreal points, Qt::Orientation orientation = Qt::Horizontal); + + const char *name() const; + + bool canRender(const QChar *string, int len); + + Type type() const; + + void getCharacterData(glyph_t glyph, TOpenFontCharMetrics& metrics, const TUint8*& bitmap, TSize& bitmapSize) const; + void setFontScale(qreal scale); + +private: + friend class QFontPrivate; + friend class QSymbianVGFontGlyphCache; + + QFixed glyphAdvance(HB_Glyph glyph) const; + CFont *fontWithSize(qreal size) const; + static void releaseFont(CFont *&font); + + const QSymbianTypeFaceExtras *m_extras; + CFont* m_originalFont; + const qreal m_originalFontSizeInPixels; + CFont* m_scaledFont; + qreal m_scaledFontSizeInPixels; + CFont* m_activeFont; +}; + +class QFontEngineMultiS60 : public QFontEngineMulti +{ +public: + QFontEngineMultiS60(QFontEngine *first, int script, const QStringList &fallbackFamilies); + void loadEngine(int at); + + int m_script; + QStringList m_fallbackFamilies; +}; + +QT_END_NAMESPACE + +#endif // QFONTENGINE_S60_P_H diff --git a/src/gui/text/qfontengine_win.cpp b/src/gui/text/qfontengine_win.cpp new file mode 100644 index 0000000000..82d9da0be9 --- /dev/null +++ b/src/gui/text/qfontengine_win.cpp @@ -0,0 +1,1322 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#if _WIN32_WINNT < 0x0500 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0500 +#endif + +#include "qfontengine_p.h" +#include "qtextengine_p.h" +#include +#include "qt_windows.h" +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include "qpaintengine.h" +#include "qvarlengtharray.h" +#include +#include + +#if defined(Q_WS_WINCE) +#include "qguifunctions_wince.h" +#endif + +//### mingw needed define +#ifndef TT_PRIM_CSPLINE +#define TT_PRIM_CSPLINE 3 +#endif + +#ifdef MAKE_TAG +#undef MAKE_TAG +#endif +// GetFontData expects the tags in little endian ;( +#define MAKE_TAG(ch1, ch2, ch3, ch4) (\ + (((quint32)(ch4)) << 24) | \ + (((quint32)(ch3)) << 16) | \ + (((quint32)(ch2)) << 8) | \ + ((quint32)(ch1)) \ + ) + +// common DC for all fonts + +QT_BEGIN_NAMESPACE + +class QtHDC +{ + HDC _hdc; +public: + QtHDC() + { + HDC displayDC = GetDC(0); + _hdc = CreateCompatibleDC(displayDC); + ReleaseDC(0, displayDC); + } + ~QtHDC() + { + if (_hdc) + DeleteDC(_hdc); + } + HDC hdc() const + { + return _hdc; + } +}; + +#ifndef QT_NO_THREAD +Q_GLOBAL_STATIC(QThreadStorage, local_shared_dc) +HDC shared_dc() +{ + QtHDC *&hdc = local_shared_dc()->localData(); + if (!hdc) + hdc = new QtHDC; + return hdc->hdc(); +} +#else +HDC shared_dc() +{ + return 0; +} +#endif + +#ifndef Q_WS_WINCE +typedef BOOL (WINAPI *PtrGetCharWidthI)(HDC, UINT, UINT, LPWORD, LPINT); +static PtrGetCharWidthI ptrGetCharWidthI = 0; +static bool resolvedGetCharWidthI = false; + +static void resolveGetCharWidthI() +{ + if (resolvedGetCharWidthI) + return; + resolvedGetCharWidthI = true; + ptrGetCharWidthI = (PtrGetCharWidthI)QSystemLibrary::resolve(QLatin1String("gdi32"), "GetCharWidthI"); +} +#endif // !defined(Q_WS_WINCE) + +// defined in qtextengine_win.cpp +typedef void *SCRIPT_CACHE; +typedef HRESULT (WINAPI *fScriptFreeCache)(SCRIPT_CACHE *); +extern fScriptFreeCache ScriptFreeCache; + +static inline quint32 getUInt(unsigned char *p) +{ + quint32 val; + val = *p++ << 24; + val |= *p++ << 16; + val |= *p++ << 8; + val |= *p; + + return val; +} + +static inline quint16 getUShort(unsigned char *p) +{ + quint16 val; + val = *p++ << 8; + val |= *p; + + return val; +} + +// general font engine + +QFixed QFontEngineWin::lineThickness() const +{ + if(lineWidth > 0) + return lineWidth; + + return QFontEngine::lineThickness(); +} + +static OUTLINETEXTMETRIC *getOutlineTextMetric(HDC hdc) +{ + int size; + size = GetOutlineTextMetrics(hdc, 0, 0); + OUTLINETEXTMETRIC *otm = (OUTLINETEXTMETRIC *)malloc(size); + GetOutlineTextMetrics(hdc, size, otm); + return otm; +} + +void QFontEngineWin::getCMap() +{ + ttf = (bool)(tm.tmPitchAndFamily & TMPF_TRUETYPE); + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + bool symb = false; + if (ttf) { + cmapTable = getSfntTable(qbswap(MAKE_TAG('c', 'm', 'a', 'p'))); + int size = 0; + cmap = QFontEngine::getCMap(reinterpret_cast(cmapTable.constData()), + cmapTable.size(), &symb, &size); + } + if (!cmap) { + ttf = false; + symb = false; + } + symbol = symb; + designToDevice = 1; + _faceId.index = 0; + if(cmap) { + OUTLINETEXTMETRIC *otm = getOutlineTextMetric(hdc); + designToDevice = QFixed((int)otm->otmEMSquare)/int(otm->otmTextMetrics.tmHeight); + unitsPerEm = otm->otmEMSquare; + x_height = (int)otm->otmsXHeight; + loadKerningPairs(designToDevice); + _faceId.filename = QString::fromWCharArray((wchar_t *)((char *)otm + (quintptr)otm->otmpFullName)).toLatin1(); + lineWidth = otm->otmsUnderscoreSize; + fsType = otm->otmfsType; + free(otm); + } else { + unitsPerEm = tm.tmHeight; + } +} + + +inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +int QFontEngineWin::getGlyphIndexes(const QChar *str, int numChars, QGlyphLayout *glyphs, bool mirrored) const +{ + int i = 0; + int glyph_pos = 0; + if (mirrored) { +#if defined(Q_WS_WINCE) + { +#else + if (symbol) { + for (; i < numChars; ++i, ++glyph_pos) { + unsigned int uc = getChar(str, i, numChars); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc); + if (!glyphs->glyphs[glyph_pos] && uc < 0x100) + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + } + } else if (ttf) { + for (; i < numChars; ++i, ++glyph_pos) { + unsigned int uc = getChar(str, i, numChars); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, QChar::mirroredChar(uc)); + } + } else { +#endif + wchar_t first = tm.tmFirstChar; + wchar_t last = tm.tmLastChar; + + for (; i < numChars; ++i, ++glyph_pos) { + uint ucs = QChar::mirroredChar(getChar(str, i, numChars)); + if ( +#ifdef Q_WS_WINCE + tm.tmFirstChar > 60000 || // see line 375 +#endif + ucs >= first && ucs <= last) + glyphs->glyphs[glyph_pos] = ucs; + else + glyphs->glyphs[glyph_pos] = 0; + } + } + } else { +#if defined(Q_WS_WINCE) + { +#else + if (symbol) { + for (; i < numChars; ++i, ++glyph_pos) { + unsigned int uc = getChar(str, i, numChars); + glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc); + if(!glyphs->glyphs[glyph_pos] && uc < 0x100) + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + } + } else if (ttf) { + for (; i < numChars; ++i, ++glyph_pos) { + unsigned int uc = getChar(str, i, numChars); + glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, uc); + } + } else { +#endif + wchar_t first = tm.tmFirstChar; + wchar_t last = tm.tmLastChar; + + for (; i < numChars; ++i, ++glyph_pos) { + uint uc = getChar(str, i, numChars); + if ( +#ifdef Q_WS_WINCE + tm.tmFirstChar > 60000 || // see comment in QFontEngineWin +#endif + uc >= first && uc <= last) + glyphs->glyphs[glyph_pos] = uc; + else + glyphs->glyphs[glyph_pos] = 0; + } + } + } + glyphs->numGlyphs = glyph_pos; + return glyph_pos; +} + + +QFontEngineWin::QFontEngineWin(const QString &name, HFONT _hfont, bool stockFont, LOGFONT lf) +{ + //qDebug("regular windows font engine created: font='%s', size=%d", name, lf.lfHeight); + + _name = name; + + cmap = 0; + hfont = _hfont; + logfont = lf; + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + this->stockFont = stockFont; + fontDef.pixelSize = -lf.lfHeight; + + lbearing = SHRT_MIN; + rbearing = SHRT_MIN; + synthesized_flags = -1; + lineWidth = -1; + x_height = -1; + + BOOL res = GetTextMetrics(hdc, &tm); + fontDef.fixedPitch = !(tm.tmPitchAndFamily & TMPF_FIXED_PITCH); + if (!res) { + qErrnoWarning("QFontEngineWin: GetTextMetrics failed"); + ZeroMemory(&tm, sizeof(TEXTMETRIC)); + } + + cache_cost = tm.tmHeight * tm.tmAveCharWidth * 2000; + getCMap(); + + widthCache = 0; + widthCacheSize = 0; + designAdvances = 0; + designAdvancesSize = 0; + +#ifndef Q_WS_WINCE + if (!resolvedGetCharWidthI) + resolveGetCharWidthI(); +#endif +} + +QFontEngineWin::~QFontEngineWin() +{ + if (designAdvances) + free(designAdvances); + + if (widthCache) + free(widthCache); + + // make sure we aren't by accident still selected + SelectObject(shared_dc(), (HFONT)GetStockObject(SYSTEM_FONT)); + + if (!stockFont) { + if (!DeleteObject(hfont)) + qErrnoWarning("QFontEngineWin: failed to delete non-stock font..."); + } +} + +HGDIOBJ QFontEngineWin::selectDesignFont() const +{ + LOGFONT f = logfont; + f.lfHeight = unitsPerEm; + HFONT designFont = CreateFontIndirect(&f); + return SelectObject(shared_dc(), designFont); +} + +bool QFontEngineWin::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + + *nglyphs = getGlyphIndexes(str, len, glyphs, flags & QTextEngine::RightToLeft); + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + recalcAdvances(glyphs, flags); + return true; +} + +inline void calculateTTFGlyphWidth(HDC hdc, UINT glyph, int &width) +{ +#if defined(Q_WS_WINCE) + GetCharWidth32(hdc, glyph, glyph, &width); +#else + if (ptrGetCharWidthI) + ptrGetCharWidthI(hdc, glyph, 1, 0, &width); +#endif +} + +void QFontEngineWin::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + HGDIOBJ oldFont = 0; + HDC hdc = shared_dc(); + if (ttf && (flags & QTextEngine::DesignMetrics)) { + for(int i = 0; i < glyphs->numGlyphs; i++) { + unsigned int glyph = glyphs->glyphs[i]; + if(int(glyph) >= designAdvancesSize) { + int newSize = (glyph + 256) >> 8 << 8; + designAdvances = q_check_ptr((QFixed *)realloc(designAdvances, + newSize*sizeof(QFixed))); + for(int i = designAdvancesSize; i < newSize; ++i) + designAdvances[i] = -1000000; + designAdvancesSize = newSize; + } + if (designAdvances[glyph] < -999999) { + if (!oldFont) + oldFont = selectDesignFont(); + + int width = 0; + calculateTTFGlyphWidth(hdc, glyph, width); + designAdvances[glyph] = QFixed(width) / designToDevice; + } + glyphs->advances_x[i] = designAdvances[glyph]; + glyphs->advances_y[i] = 0; + } + if(oldFont) + DeleteObject(SelectObject(hdc, oldFont)); + } else { + for(int i = 0; i < glyphs->numGlyphs; i++) { + unsigned int glyph = glyphs->glyphs[i]; + + glyphs->advances_y[i] = 0; + + if (glyph >= widthCacheSize) { + int newSize = (glyph + 256) >> 8 << 8; + widthCache = q_check_ptr((unsigned char *)realloc(widthCache, + newSize*sizeof(QFixed))); + memset(widthCache + widthCacheSize, 0, newSize - widthCacheSize); + widthCacheSize = newSize; + } + glyphs->advances_x[i] = widthCache[glyph]; + // font-width cache failed + if (glyphs->advances_x[i] == 0) { + int width = 0; + if (!oldFont) + oldFont = SelectObject(hdc, hfont); + + if (!ttf) { + QChar ch[2] = { ushort(glyph), 0 }; + int chrLen = 1; + if (glyph > 0xffff) { + ch[0] = QChar::highSurrogate(glyph); + ch[1] = QChar::lowSurrogate(glyph); + ++chrLen; + } + SIZE size = {0, 0}; + GetTextExtentPoint32(hdc, (wchar_t *)ch, chrLen, &size); + width = size.cx; + } else { + calculateTTFGlyphWidth(hdc, glyph, width); + } + glyphs->advances_x[i] = width; + // if glyph's within cache range, store it for later + if (width > 0 && width < 0x100) + widthCache[glyph] = width; + } + } + + if (oldFont) + SelectObject(hdc, oldFont); + } +} + +glyph_metrics_t QFontEngineWin::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs == 0) + return glyph_metrics_t(); + + QFixed w = 0; + for (int i = 0; i < glyphs.numGlyphs; ++i) + w += glyphs.effectiveAdvance(i); + + return glyph_metrics_t(0, -tm.tmAscent, w - lastRightBearing(glyphs), tm.tmHeight, w, 0); +} + +#ifndef Q_WS_WINCE +bool QFontEngineWin::getOutlineMetrics(glyph_t glyph, const QTransform &t, glyph_metrics_t *metrics) const +{ + Q_ASSERT(metrics != 0); + + HDC hdc = shared_dc(); + + GLYPHMETRICS gm; + DWORD res = 0; + MAT2 mat; + mat.eM11.value = mat.eM22.value = 1; + mat.eM11.fract = mat.eM22.fract = 0; + mat.eM21.value = mat.eM12.value = 0; + mat.eM21.fract = mat.eM12.fract = 0; + + if (t.type() > QTransform::TxTranslate) { + // We need to set the transform using the HDC's world + // matrix rather than using the MAT2 above, because the + // results provided when transforming via MAT2 does not + // match the glyphs that are drawn using a WorldTransform + XFORM xform; + xform.eM11 = t.m11(); + xform.eM12 = t.m12(); + xform.eM21 = t.m21(); + xform.eM22 = t.m22(); + xform.eDx = 0; + xform.eDy = 0; + SetGraphicsMode(hdc, GM_ADVANCED); + SetWorldTransform(hdc, &xform); + } + + uint format = GGO_METRICS; + if (ttf) + format |= GGO_GLYPH_INDEX; + res = GetGlyphOutline(hdc, glyph, format, &gm, 0, 0, &mat); + + if (t.type() > QTransform::TxTranslate) { + XFORM xform; + xform.eM11 = xform.eM22 = 1; + xform.eM12 = xform.eM21 = xform.eDx = xform.eDy = 0; + SetWorldTransform(hdc, &xform); + SetGraphicsMode(hdc, GM_COMPATIBLE); + } + + if (res != GDI_ERROR) { + *metrics = glyph_metrics_t(gm.gmptGlyphOrigin.x, -gm.gmptGlyphOrigin.y, + (int)gm.gmBlackBoxX, (int)gm.gmBlackBoxY, gm.gmCellIncX, gm.gmCellIncY); + return true; + } else { + return false; + } +} +#endif + +glyph_metrics_t QFontEngineWin::boundingBox(glyph_t glyph, const QTransform &t) +{ +#ifndef Q_WS_WINCE + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + + glyph_metrics_t glyphMetrics; + bool success = getOutlineMetrics(glyph, t, &glyphMetrics); + + if (!ttf && !success) { + // Bitmap fonts + wchar_t ch = glyph; + ABCFLOAT abc; + GetCharABCWidthsFloat(hdc, ch, ch, &abc); + int width = qRound(abc.abcfB); + + return glyph_metrics_t(QFixed::fromReal(abc.abcfA), -tm.tmAscent, width, tm.tmHeight, width, 0).transformed(t); + } + + return glyphMetrics; +#else + HDC hdc = shared_dc(); + HGDIOBJ oldFont = SelectObject(hdc, hfont); + + ABC abc; + int width; + int advance; +#ifdef GWES_MGTT // true type fonts + if (GetCharABCWidths(hdc, glyph, glyph, &abc)) { + width = qAbs(abc.abcA) + abc.abcB + qAbs(abc.abcC); + advance = abc.abcA + abc.abcB + abc.abcC; + } + else +#endif +#if defined(GWES_MGRAST) || defined(GWES_MGRAST2) // raster fonts + if (GetCharWidth32(hdc, glyph, glyph, &width)) { + advance = width; + } + else +#endif + { // fallback + width = tm.tmMaxCharWidth; + advance = width; + } + + SelectObject(hdc, oldFont); + return glyph_metrics_t(0, -tm.tmAscent, width, tm.tmHeight, advance, 0).transformed(t); +#endif +} + +QFixed QFontEngineWin::ascent() const +{ + return tm.tmAscent; +} + +QFixed QFontEngineWin::descent() const +{ + // ### we substract 1 to even out the historical +1 in QFontMetrics's + // ### height=asc+desc+1 equation. Fix in Qt5. + return tm.tmDescent - 1; +} + +QFixed QFontEngineWin::leading() const +{ + return tm.tmExternalLeading; +} + + +QFixed QFontEngineWin::xHeight() const +{ + if(x_height >= 0) + return x_height; + return QFontEngine::xHeight(); +} + +QFixed QFontEngineWin::averageCharWidth() const +{ + return tm.tmAveCharWidth; +} + +qreal QFontEngineWin::maxCharWidth() const +{ + return tm.tmMaxCharWidth; +} + +enum { max_font_count = 256 }; +static const ushort char_table[] = { + 40, + 67, + 70, + 75, + 86, + 88, + 89, + 91, + 102, + 114, + 124, + 127, + 205, + 645, + 884, + 922, + 1070, + 12386, + 0 +}; + +static const int char_table_entries = sizeof(char_table)/sizeof(ushort); + +#ifndef Q_CC_MINGW +void QFontEngineWin::getGlyphBearings(glyph_t glyph, qreal *leftBearing, qreal *rightBearing) +{ + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + +#ifndef Q_WS_WINCE + if (ttf) +#endif + + { + ABC abcWidths; + GetCharABCWidthsI(hdc, glyph, 1, 0, &abcWidths); + if (leftBearing) + *leftBearing = abcWidths.abcA; + if (rightBearing) + *rightBearing = abcWidths.abcC; + } + +#ifndef Q_WS_WINCE + else { + QFontEngine::getGlyphBearings(glyph, leftBearing, rightBearing); + } +#endif +} +#endif // Q_CC_MINGW + +qreal QFontEngineWin::minLeftBearing() const +{ + if (lbearing == SHRT_MIN) + minRightBearing(); // calculates both + + return lbearing; +} + +qreal QFontEngineWin::minRightBearing() const +{ +#ifdef Q_WS_WINCE + if (rbearing == SHRT_MIN) { + int ml = 0; + int mr = 0; + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + if (ttf) { + ABC *abc = 0; + int n = tm.tmLastChar - tm.tmFirstChar; + if (n <= max_font_count) { + abc = new ABC[n+1]; + GetCharABCWidths(hdc, tm.tmFirstChar, tm.tmLastChar, abc); + } else { + abc = new ABC[char_table_entries+1]; + for(int i = 0; i < char_table_entries; i++) + GetCharABCWidths(hdc, char_table[i], char_table[i], abc+i); + n = char_table_entries; + } + ml = abc[0].abcA; + mr = abc[0].abcC; + for (int i = 1; i < n; i++) { + if (abc[i].abcA + abc[i].abcB + abc[i].abcC != 0) { + ml = qMin(ml,abc[i].abcA); + mr = qMin(mr,abc[i].abcC); + } + } + delete [] abc; + } + lbearing = ml; + rbearing = mr; + } + + return rbearing; +#else + if (rbearing == SHRT_MIN) { + int ml = 0; + int mr = 0; + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + if (ttf) { + ABC *abc = 0; + int n = tm.tmLastChar - tm.tmFirstChar; + if (n <= max_font_count) { + abc = new ABC[n+1]; + GetCharABCWidths(hdc, tm.tmFirstChar, tm.tmLastChar, abc); + } else { + abc = new ABC[char_table_entries+1]; + for(int i = 0; i < char_table_entries; i++) + GetCharABCWidths(hdc, char_table[i], char_table[i], abc + i); + n = char_table_entries; + } + ml = abc[0].abcA; + mr = abc[0].abcC; + for (int i = 1; i < n; i++) { + if (abc[i].abcA + abc[i].abcB + abc[i].abcC != 0) { + ml = qMin(ml,abc[i].abcA); + mr = qMin(mr,abc[i].abcC); + } + } + delete [] abc; + } else { + ABCFLOAT *abc = 0; + int n = tm.tmLastChar - tm.tmFirstChar+1; + if (n <= max_font_count) { + abc = new ABCFLOAT[n]; + GetCharABCWidthsFloat(hdc, tm.tmFirstChar, tm.tmLastChar, abc); + } else { + abc = new ABCFLOAT[char_table_entries]; + for(int i = 0; i < char_table_entries; i++) + GetCharABCWidthsFloat(hdc, char_table[i], char_table[i], abc+i); + n = char_table_entries; + } + float fml = abc[0].abcfA; + float fmr = abc[0].abcfC; + for (int i=1; i string->unicode() || tm.tmLastChar < string->unicode()) + return false; + } + } + return true; +} + +QFontEngine::Type QFontEngineWin::type() const +{ + return QFontEngine::Win; +} + +static inline double qt_fixed_to_double(const FIXED &p) { + return ((p.value << 16) + p.fract) / 65536.0; +} + +static inline QPointF qt_to_qpointf(const POINTFX &pt, qreal scale) { + return QPointF(qt_fixed_to_double(pt.x) * scale, -qt_fixed_to_double(pt.y) * scale); +} + +#ifndef GGO_UNHINTED +#define GGO_UNHINTED 0x0100 +#endif + +static bool addGlyphToPath(glyph_t glyph, const QFixedPoint &position, HDC hdc, + QPainterPath *path, bool ttf, glyph_metrics_t *metric = 0, qreal scale = 1) +{ +#if defined(Q_WS_WINCE) + Q_UNUSED(glyph); + Q_UNUSED(hdc); +#endif + MAT2 mat; + mat.eM11.value = mat.eM22.value = 1; + mat.eM11.fract = mat.eM22.fract = 0; + mat.eM21.value = mat.eM12.value = 0; + mat.eM21.fract = mat.eM12.fract = 0; + uint glyphFormat = GGO_NATIVE; + + if (ttf) + glyphFormat |= GGO_GLYPH_INDEX; + + GLYPHMETRICS gMetric; + memset(&gMetric, 0, sizeof(GLYPHMETRICS)); + int bufferSize = GDI_ERROR; +#if !defined(Q_WS_WINCE) + bufferSize = GetGlyphOutline(hdc, glyph, glyphFormat, &gMetric, 0, 0, &mat); +#endif + if ((DWORD)bufferSize == GDI_ERROR) { + return false; + } + + void *dataBuffer = new char[bufferSize]; + DWORD ret = GDI_ERROR; +#if !defined(Q_WS_WINCE) + ret = GetGlyphOutline(hdc, glyph, glyphFormat, &gMetric, bufferSize, dataBuffer, &mat); +#endif + if (ret == GDI_ERROR) { + delete [](char *)dataBuffer; + return false; + } + + if(metric) { + // #### obey scale + *metric = glyph_metrics_t(gMetric.gmptGlyphOrigin.x, -gMetric.gmptGlyphOrigin.y, + (int)gMetric.gmBlackBoxX, (int)gMetric.gmBlackBoxY, + gMetric.gmCellIncX, gMetric.gmCellIncY); + } + + int offset = 0; + int headerOffset = 0; + TTPOLYGONHEADER *ttph = 0; + + QPointF oset = position.toPointF(); + while (headerOffset < bufferSize) { + ttph = (TTPOLYGONHEADER*)((char *)dataBuffer + headerOffset); + + QPointF lastPoint(qt_to_qpointf(ttph->pfxStart, scale)); + path->moveTo(lastPoint + oset); + offset += sizeof(TTPOLYGONHEADER); + TTPOLYCURVE *curve; + while (offsetcb)) { + curve = (TTPOLYCURVE*)((char*)(dataBuffer) + offset); + switch (curve->wType) { + case TT_PRIM_LINE: { + for (int i=0; icpfx; ++i) { + QPointF p = qt_to_qpointf(curve->apfx[i], scale) + oset; + path->lineTo(p); + } + break; + } + case TT_PRIM_QSPLINE: { + const QPainterPath::Element &elm = path->elementAt(path->elementCount()-1); + QPointF prev(elm.x, elm.y); + QPointF endPoint; + for (int i=0; icpfx - 1; ++i) { + QPointF p1 = qt_to_qpointf(curve->apfx[i], scale) + oset; + QPointF p2 = qt_to_qpointf(curve->apfx[i+1], scale) + oset; + if (i < curve->cpfx - 2) { + endPoint = QPointF((p1.x() + p2.x()) / 2, (p1.y() + p2.y()) / 2); + } else { + endPoint = p2; + } + + path->quadTo(p1, endPoint); + prev = endPoint; + } + + break; + } + case TT_PRIM_CSPLINE: { + for (int i=0; icpfx; ) { + QPointF p2 = qt_to_qpointf(curve->apfx[i++], scale) + oset; + QPointF p3 = qt_to_qpointf(curve->apfx[i++], scale) + oset; + QPointF p4 = qt_to_qpointf(curve->apfx[i++], scale) + oset; + path->cubicTo(p2, p3, p4); + } + break; + } + default: + qWarning("QFontEngineWin::addOutlineToPath, unhandled switch case"); + } + offset += sizeof(TTPOLYCURVE) + (curve->cpfx-1) * sizeof(POINTFX); + } + path->closeSubpath(); + headerOffset += ttph->cb; + } + delete [] (char*)dataBuffer; + + return true; +} + +void QFontEngineWin::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, + QPainterPath *path, QTextItem::RenderFlags) +{ + LOGFONT lf = logfont; + // The sign must be negative here to make sure we match against character height instead of + // hinted cell height. This ensures that we get linear matching, and we need this for + // paths since we later on apply a scaling transform to the glyph outline to get the + // font at the correct pixel size. + lf.lfHeight = -unitsPerEm; + lf.lfWidth = 0; + HFONT hf = CreateFontIndirect(&lf); + HDC hdc = shared_dc(); + HGDIOBJ oldfont = SelectObject(hdc, hf); + + for(int i = 0; i < nglyphs; ++i) { + if (!addGlyphToPath(glyphs[i], positions[i], hdc, path, ttf, /*metric*/0, + qreal(fontDef.pixelSize) / unitsPerEm)) { + // Some windows fonts, like "Modern", are vector stroke + // fonts, which are reported as TMPF_VECTOR but do not + // support GetGlyphOutline, and thus we set this bit so + // that addOutLineToPath can check it and return safely... + hasOutline = false; + break; + } + } + DeleteObject(SelectObject(hdc, oldfont)); +} + +void QFontEngineWin::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ +#if !defined(Q_WS_WINCE) + if(tm.tmPitchAndFamily & (TMPF_TRUETYPE | TMPF_VECTOR)) { + hasOutline = true; + QFontEngine::addOutlineToPath(x, y, glyphs, path, flags); + if (hasOutline) { + // has_outline is set to false if addGlyphToPath gets + // false from GetGlyphOutline, meaning its not an outline + // font. + return; + } + } +#endif + QFontEngine::addBitmapFontToPath(x, y, glyphs, path, flags); +} + +QFontEngine::FaceId QFontEngineWin::faceId() const +{ + return _faceId; +} + +QT_BEGIN_INCLUDE_NAMESPACE +#include +QT_END_INCLUDE_NAMESPACE + +int QFontEngineWin::synthesized() const +{ + if(synthesized_flags == -1) { + synthesized_flags = 0; + if(ttf) { + const DWORD HEAD = MAKE_TAG('h', 'e', 'a', 'd'); + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + uchar data[4]; + GetFontData(hdc, HEAD, 44, &data, 4); + USHORT macStyle = getUShort(data); + if (tm.tmItalic && !(macStyle & 2)) + synthesized_flags = SynthesizedItalic; + if (fontDef.stretch != 100 && ttf) + synthesized_flags |= SynthesizedStretch; + if (tm.tmWeight >= 500 && !(macStyle & 1)) + synthesized_flags |= SynthesizedBold; + //qDebug() << "font is" << _name << + // "it=" << (macStyle & 2) << fontDef.style << "flags=" << synthesized_flags; + } + } + return synthesized_flags; +} + +QFixed QFontEngineWin::emSquareSize() const +{ + return unitsPerEm; +} + +QFontEngine::Properties QFontEngineWin::properties() const +{ + LOGFONT lf = logfont; + lf.lfHeight = unitsPerEm; + HFONT hf = CreateFontIndirect(&lf); + HDC hdc = shared_dc(); + HGDIOBJ oldfont = SelectObject(hdc, hf); + OUTLINETEXTMETRIC *otm = getOutlineTextMetric(hdc); + Properties p; + p.emSquare = unitsPerEm; + p.italicAngle = otm->otmItalicAngle; + p.postscriptName = QString::fromWCharArray((wchar_t *)((char *)otm + (quintptr)otm->otmpFamilyName)).toLatin1(); + p.postscriptName += QString::fromWCharArray((wchar_t *)((char *)otm + (quintptr)otm->otmpStyleName)).toLatin1(); + p.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(p.postscriptName); + p.boundingBox = QRectF(otm->otmrcFontBox.left, -otm->otmrcFontBox.top, + otm->otmrcFontBox.right - otm->otmrcFontBox.left, + otm->otmrcFontBox.top - otm->otmrcFontBox.bottom); + p.ascent = otm->otmAscent; + p.descent = -otm->otmDescent; + p.leading = (int)otm->otmLineGap; + p.capHeight = 0; + p.lineWidth = otm->otmsUnderscoreSize; + free(otm); + DeleteObject(SelectObject(hdc, oldfont)); + return p; +} + +void QFontEngineWin::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) +{ + LOGFONT lf = logfont; + lf.lfHeight = unitsPerEm; + int flags = synthesized(); + if(flags & SynthesizedItalic) + lf.lfItalic = false; + lf.lfWidth = 0; + HFONT hf = CreateFontIndirect(&lf); + HDC hdc = shared_dc(); + HGDIOBJ oldfont = SelectObject(hdc, hf); + QFixedPoint p; + p.x = 0; + p.y = 0; + addGlyphToPath(glyph, p, hdc, path, ttf, metrics); + DeleteObject(SelectObject(hdc, oldfont)); +} + +bool QFontEngineWin::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + if (!ttf) + return false; + HDC hdc = shared_dc(); + SelectObject(hdc, hfont); + DWORD t = qbswap(tag); + *length = GetFontData(hdc, t, 0, buffer, *length); + return *length != GDI_ERROR; +} + +#if !defined(CLEARTYPE_QUALITY) +# define CLEARTYPE_QUALITY 5 +#endif + +extern bool qt_cleartype_enabled; + +QNativeImage *QFontEngineWin::drawGDIGlyph(HFONT font, glyph_t glyph, int margin, + const QTransform &t, QImage::Format mask_format) +{ + Q_UNUSED(mask_format) + glyph_metrics_t gm = boundingBox(glyph); + +// printf(" -> for glyph %4x\n", glyph); + + int gx = gm.x.toInt(); + int gy = gm.y.toInt(); + int iw = gm.width.toInt(); + int ih = gm.height.toInt(); + + if (iw <= 0 || iw <= 0) + return 0; + + bool has_transformation = t.type() > QTransform::TxTranslate; + +#ifndef Q_WS_WINCE + unsigned int options = ttf ? ETO_GLYPH_INDEX : 0; + XFORM xform; + + if (has_transformation) { + xform.eM11 = t.m11(); + xform.eM12 = t.m12(); + xform.eM21 = t.m21(); + xform.eM22 = t.m22(); + xform.eDx = margin; + xform.eDy = margin; + + QtHDC qthdc; + HDC hdc = qthdc.hdc(); + + SetGraphicsMode(hdc, GM_ADVANCED); + SetWorldTransform(hdc, &xform); + HGDIOBJ old_font = SelectObject(hdc, font); + + int ggo_options = GGO_METRICS | (ttf ? GGO_GLYPH_INDEX : 0); + GLYPHMETRICS tgm; + MAT2 mat; + memset(&mat, 0, sizeof(mat)); + mat.eM11.value = mat.eM22.value = 1; + + if (GetGlyphOutline(hdc, glyph, ggo_options, &tgm, 0, 0, &mat) == GDI_ERROR) { + qWarning("QWinFontEngine: unable to query transformed glyph metrics..."); + return 0; + } + + iw = tgm.gmBlackBoxX; + ih = tgm.gmBlackBoxY; + + xform.eDx -= tgm.gmptGlyphOrigin.x; + xform.eDy += tgm.gmptGlyphOrigin.y; + + SetGraphicsMode(hdc, GM_COMPATIBLE); + SelectObject(hdc, old_font); + } +#else // else winc + unsigned int options = 0; +#ifdef DEBUG + Q_ASSERT(!has_transformation); +#else + Q_UNUSED(has_transformation); +#endif +#endif + + QNativeImage *ni = new QNativeImage(iw + 2 * margin + 4, + ih + 2 * margin + 4, + QNativeImage::systemFormat(), !qt_cleartype_enabled); + + /*If cleartype is enabled we use the standard system format even on Windows CE + and not the special textbuffer format we have to use if cleartype is disabled*/ + + ni->image.fill(0xffffffff); + + HDC hdc = ni->hdc; + + SelectObject(hdc, GetStockObject(NULL_BRUSH)); + SelectObject(hdc, GetStockObject(BLACK_PEN)); + SetTextColor(hdc, RGB(0,0,0)); + SetBkMode(hdc, TRANSPARENT); + SetTextAlign(hdc, TA_BASELINE); + + HGDIOBJ old_font = SelectObject(hdc, font); + +#ifndef Q_OS_WINCE + if (has_transformation) { + SetGraphicsMode(hdc, GM_ADVANCED); + SetWorldTransform(hdc, &xform); + ExtTextOut(hdc, 0, 0, options, 0, (LPCWSTR) &glyph, 1, 0); + } else +#endif + { + ExtTextOut(hdc, -gx + margin, -gy + margin, options, 0, (LPCWSTR) &glyph, 1, 0); + } + + SelectObject(hdc, old_font); + return ni; +} + + +extern uint qt_pow_gamma[256]; + +QImage QFontEngineWin::alphaMapForGlyph(glyph_t glyph, const QTransform &xform) +{ + HFONT font = hfont; + if (qt_cleartype_enabled) { + LOGFONT lf = logfont; + lf.lfQuality = ANTIALIASED_QUALITY; + font = CreateFontIndirect(&lf); + } + QImage::Format mask_format = QNativeImage::systemFormat(); +#ifndef Q_OS_WINCE + mask_format = QImage::Format_RGB32; +#endif + + QNativeImage *mask = drawGDIGlyph(font, glyph, 0, xform, mask_format); + if (mask == 0) + return QImage(); + + QImage indexed(mask->width(), mask->height(), QImage::Format_Indexed8); + + // ### This part is kinda pointless, but we'll crash later if we don't because some + // code paths expects there to be colortables for index8-bit... + QVector colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + // Copy data... Cannot use QPainter here as GDI has messed up the + // Alpha channel of the ni.image pixels... + for (int y=0; yheight(); ++y) { + uchar *dest = indexed.scanLine(y); + if (mask->image.format() == QImage::Format_RGB16) { + const qint16 *src = (qint16 *) ((const QImage &) mask->image).scanLine(y); + for (int x=0; xwidth(); ++x) + dest[x] = 255 - qGray(src[x]); + } else { + const uint *src = (uint *) ((const QImage &) mask->image).scanLine(y); + for (int x=0; xwidth(); ++x) { +#ifdef Q_OS_WINCE + dest[x] = 255 - qGray(src[x]); +#else + if (QNativeImage::systemFormat() == QImage::Format_RGB16) + dest[x] = 255 - qGray(src[x]); + else + dest[x] = 255 - (qt_pow_gamma[qGray(src[x])] * 255. / 2047.); +#endif + } + } + } + + // Cleanup... + delete mask; + if (qt_cleartype_enabled) { + DeleteObject(font); + } + + return indexed; +} + +#define SPI_GETFONTSMOOTHINGCONTRAST 0x200C +#define SPI_SETFONTSMOOTHINGCONTRAST 0x200D + +QImage QFontEngineWin::alphaRGBMapForGlyph(glyph_t glyph, QFixed, int margin, const QTransform &t) +{ + HFONT font = hfont; + + int contrast; + SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &contrast, 0); + SystemParametersInfo(SPI_SETFONTSMOOTHINGCONTRAST, 0, (void *) 1000, 0); + + QNativeImage *mask = drawGDIGlyph(font, glyph, margin, t, QImage::Format_RGB32); + SystemParametersInfo(SPI_SETFONTSMOOTHINGCONTRAST, 0, (void *) contrast, 0); + + if (mask == 0) + return QImage(); + + // Gracefully handle the odd case when the display is 16-bit + const QImage source = mask->image.depth() == 32 + ? mask->image + : mask->image.convertToFormat(QImage::Format_RGB32); + + QImage rgbMask(mask->width(), mask->height(), QImage::Format_RGB32); + for (int y=0; yheight(); ++y) { + uint *dest = (uint *) rgbMask.scanLine(y); + const uint *src = (uint *) source.scanLine(y); + for (int x=0; xwidth(); ++x) { + dest[x] = 0xffffffff - (0x00ffffff & src[x]); + } + } + + delete mask; + + return rgbMask; +} + +// -------------------------------------- Multi font engine + +QFontEngineMultiWin::QFontEngineMultiWin(QFontEngine *first, const QStringList &fallbacks) + : QFontEngineMulti(fallbacks.size()+1), + fallbacks(fallbacks) +{ + engines[0] = first; + first->ref.ref(); + fontDef = engines[0]->fontDef; + cache_cost = first->cache_cost; +} + +void QFontEngineMultiWin::loadEngine(int at) +{ + Q_ASSERT(at < engines.size()); + Q_ASSERT(engines.at(at) == 0); + + QString fam = fallbacks.at(at-1); + + LOGFONT lf = static_cast(engines.at(0))->logfont; + memcpy(lf.lfFaceName, fam.utf16(), sizeof(wchar_t) * qMin(fam.length() + 1, 32)); // 32 = Windows hard-coded + HFONT hfont = CreateFontIndirect(&lf); + + bool stockFont = false; + if (hfont == 0) { + hfont = (HFONT)GetStockObject(ANSI_VAR_FONT); + stockFont = true; + } + engines[at] = new QFontEngineWin(fam, hfont, stockFont, lf); + engines[at]->ref.ref(); + engines[at]->fontDef = fontDef; + + // TODO: increase cost in QFontCache for the font engine loaded here +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_win_p.h b/src/gui/text/qfontengine_win_p.h new file mode 100644 index 0000000000..28d80008e4 --- /dev/null +++ b/src/gui/text/qfontengine_win_p.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_WIN_P_H +#define QFONTENGINE_WIN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_BEGIN_NAMESPACE + +class QNativeImage; + +class QFontEngineWin : public QFontEngine +{ +public: + QFontEngineWin(const QString &name, HFONT, bool, LOGFONT); + ~QFontEngineWin(); + + virtual QFixed lineThickness() const; + virtual Properties properties() const; + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + virtual FaceId faceId() const; + virtual bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + virtual int synthesized() const; + virtual QFixed emSquareSize() const; + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const; + + virtual void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags); + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, + QPainterPath *path, QTextItem::RenderFlags flags); + + HGDIOBJ selectDesignFont() const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t g) { return boundingBox(g, QTransform()); } + virtual glyph_metrics_t boundingBox(glyph_t g, const QTransform &t); + + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual QFixed averageCharWidth() const; + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const; + virtual qreal minRightBearing() const; + + virtual const char *name() const; + + bool canRender(const QChar *string, int len); + + Type type() const; + + virtual QImage alphaMapForGlyph(glyph_t t) { return alphaMapForGlyph(t, QTransform()); } + virtual QImage alphaMapForGlyph(glyph_t, const QTransform &xform); + virtual QImage alphaRGBMapForGlyph(glyph_t t, QFixed subPixelPosition, int margin, const QTransform &xform); + +#ifndef Q_CC_MINGW + virtual void getGlyphBearings(glyph_t glyph, qreal *leftBearing = 0, qreal *rightBearing = 0); +#endif + + int getGlyphIndexes(const QChar *ch, int numChars, QGlyphLayout *glyphs, bool mirrored) const; + void getCMap(); + +#ifndef Q_WS_WINCE + bool getOutlineMetrics(glyph_t glyph, const QTransform &t, glyph_metrics_t *metrics) const; +#endif + + QString _name; + HFONT hfont; + LOGFONT logfont; + uint stockFont : 1; + uint ttf : 1; + uint hasOutline : 1; + TEXTMETRIC tm; + int lw; + const unsigned char *cmap; + QByteArray cmapTable; + mutable qreal lbearing; + mutable qreal rbearing; + QFixed designToDevice; + int unitsPerEm; + QFixed x_height; + FaceId _faceId; + + mutable int synthesized_flags; + mutable QFixed lineWidth; + mutable unsigned char *widthCache; + mutable uint widthCacheSize; + mutable QFixed *designAdvances; + mutable int designAdvancesSize; + +private: + QNativeImage *drawGDIGlyph(HFONT font, glyph_t, int margin, const QTransform &xform, + QImage::Format mask_format); + +}; + +class QFontEngineMultiWin : public QFontEngineMulti +{ +public: + QFontEngineMultiWin(QFontEngine *first, const QStringList &fallbacks); + void loadEngine(int at); + + QStringList fallbacks; +}; + +QT_END_NAMESPACE + +#endif // QFONTENGINE_WIN_P_H diff --git a/src/gui/text/qfontengine_x11.cpp b/src/gui/text/qfontengine_x11.cpp new file mode 100644 index 0000000000..9f3f8d3d9d --- /dev/null +++ b/src/gui/text/qfontengine_x11.cpp @@ -0,0 +1,1201 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbitmap.h" + +// #define FONTENGINE_DEBUG + +#include +#include +#include +#include +#include + +#include "qfontdatabase.h" +#include "qpaintdevice.h" +#include "qpainter.h" +#include "qvarlengtharray.h" +#include "qwidget.h" +#include "qsettings.h" +#include "qfile.h" + +#include +#include "qfont.h" +#include "qfont_p.h" +#include "qfontengine_p.h" +#include + +#include +#include + +#include +#include +#include "qx11info_x11.h" +#include "qfontengine_x11_p.h" + +#include + +#include +#if defined(FT_LCD_FILTER_H) +#include FT_LCD_FILTER_H +#endif + +#if defined(FC_LCD_FILTER) + +#ifndef FC_LCD_FILTER_NONE +#define FC_LCD_FILTER_NONE FC_LCD_NONE +#endif + +#ifndef FC_LCD_FILTER_DEFAULT +#define FC_LCD_FILTER_DEFAULT FC_LCD_DEFAULT +#endif + +#ifndef FC_LCD_FILTER_LIGHT +#define FC_LCD_FILTER_LIGHT FC_LCD_LIGHT +#endif + +#ifndef FC_LCD_FILTER_LEGACY +#define FC_LCD_FILTER_LEGACY FC_LCD_LEGACY +#endif + +#endif + +QT_BEGIN_NAMESPACE + + +// ------------------------------------------------------------------ +// Multi XLFD engine +// ------------------------------------------------------------------ + +QFontEngineMultiXLFD::QFontEngineMultiXLFD(const QFontDef &r, const QList &l, int s) + : QFontEngineMulti(l.size()), encodings(l), screen(s), request(r) +{ + loadEngine(0); + fontDef = engines[0]->fontDef; +} + +QFontEngineMultiXLFD::~QFontEngineMultiXLFD() +{ } + +void QFontEngineMultiXLFD::loadEngine(int at) +{ + Q_ASSERT(at < engines.size()); + Q_ASSERT(engines.at(at) == 0); + const int encoding = encodings.at(at); + QFontEngine *fontEngine = QFontDatabase::loadXlfd(0, QUnicodeTables::Common, request, encoding); + Q_ASSERT(fontEngine != 0); + fontEngine->ref.ref(); + engines[at] = fontEngine; +} + +// ------------------------------------------------------------------ +// Xlfd font engine +// ------------------------------------------------------------------ + +#ifndef QT_NO_FREETYPE + +static QStringList *qt_fontpath = 0; + +static QStringList fontPath() +{ + if (qt_fontpath) + return *qt_fontpath; + + // append qsettings fontpath + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("Qt")); + + QStringList fontpath; + + int npaths; + char** font_path; + font_path = XGetFontPath(X11->display, &npaths); + bool xfsconfig_read = false; + for (int i=0; i read its config + bool finished = false; + QFile f(QLatin1String("/etc/X11/fs/config")); + if (!f.exists()) + f.setFileName(QLatin1String("/usr/X11R6/lib/X11/fs/config")); + if (!f.exists()) + f.setFileName(QLatin1String("/usr/X11/lib/X11/fs/config")); + if (f.exists()) { + f.open(QIODevice::ReadOnly); + while (f.error()==QFile::NoError && !finished) { + QString fs = QString::fromLocal8Bit(f.readLine(1024)); + fs=fs.trimmed(); + if (fs.left(9)==QLatin1String("catalogue") && fs.contains(QLatin1Char('='))) { + fs = fs.mid(fs.indexOf(QLatin1Char('=')) + 1).trimmed(); + bool end = false; + while (f.error()==QFile::NoError && !end) { + if (fs[int(fs.length())-1] == QLatin1Char(',')) + fs = fs.left(fs.length()-1); + else + end = true; + + fs = fs.left(fs.indexOf(QLatin1String(":unscaled"))); + if (fs[0] != QLatin1Char('#')) + fontpath += fs; + fs = QLatin1String(f.readLine(1024)); + fs = fs.trimmed(); + if (fs.isEmpty()) + end = true; + } + finished = true; + } + } + f.close(); + } + xfsconfig_read = true; + } else { + QString fs = QString::fromLocal8Bit(font_path[i]); + fontpath += fs.left(fs.indexOf(QLatin1String(":unscaled"))); + } + } + XFreeFontPath(font_path); + + // append qsettings fontpath + QStringList fp = settings.value(QLatin1String("fontPath")).toStringList(); + if (!fp.isEmpty()) + fontpath += fp; + + qt_fontpath = new QStringList(fontpath); + return fontpath; +} + +static QFontEngine::FaceId fontFile(const QByteArray &_xname, QFreetypeFace **freetype, int *synth) +{ + *freetype = 0; + *synth = 0; + + QByteArray xname = _xname.toLower(); + + int pos = 0; + int minus = 0; + while (minus < 5 && (pos = xname.indexOf('-', pos + 1))) + ++minus; + QByteArray searchname = xname.left(pos); + while (minus < 12 && (pos = xname.indexOf('-', pos + 1))) + ++minus; + QByteArray encoding = xname.mid(pos + 1); + //qDebug("xname='%s', searchname='%s', encoding='%s'", xname.data(), searchname.data(), encoding.data()); + QStringList fontpath = fontPath(); + QFontEngine::FaceId face_id; + face_id.index = 0; + + QByteArray best_mapping; + + for (QStringList::ConstIterator it = fontpath.constBegin(); it != fontpath.constEnd(); ++it) { + if (!(*it).startsWith(QLatin1Char('/'))) + continue; // not a path name, a font server + QString fontmapname; + int num = 0; + // search font.dir and font.scale for the right file + while (num < 2) { + if (num == 0) + fontmapname = (*it) + QLatin1String("/fonts.scale"); + else + fontmapname = (*it) + QLatin1String("/fonts.dir"); + ++num; + //qWarning(fontmapname); + QFile fontmap(fontmapname); + if (!fontmap.open(QIODevice::ReadOnly)) + continue; + while (!fontmap.atEnd()) { + QByteArray mapping = fontmap.readLine(); + QByteArray lmapping = mapping.toLower(); + + //qWarning(xfontname); + //qWarning(mapping); + if (!lmapping.contains(searchname)) + continue; + int index = mapping.indexOf(' '); + QByteArray ffn = mapping.mid(0,index); + // remove bitmap formats freetype can't handle + if (ffn.contains(".spd") || ffn.contains(".phont")) + continue; + bool best_match = false; + if (!best_mapping.isEmpty()) { + if (lmapping.contains("-0-0-0-0-")) { // scalable font + best_match = true; + goto found; + } + if (lmapping.contains(encoding) && !best_mapping.toLower().contains(encoding)) + goto found; + continue; + } + + found: + int colon = ffn.lastIndexOf(':'); + if (colon != -1) { + QByteArray s = ffn.left(colon); + ffn = ffn.mid(colon + 1); + if (s.contains("ds=")) + *synth |= QFontEngine::SynthesizedBold; + if (s.contains("ai=")) + *synth |= QFontEngine::SynthesizedItalic; + } + face_id.filename = (*it).toLocal8Bit() + '/' + ffn; + best_mapping = mapping; + if (best_match) + goto end; + } + } + } +end: +// qDebug("fontfile for %s is from '%s'\n got %s synth=%d", xname.data(), +// best_mapping.data(), face_id.filename.data(), *synth); + *freetype = QFreetypeFace::getFace(face_id); + if (!*freetype) { + face_id.index = 0; + face_id.filename = QByteArray(); + } + return face_id; +} + +#endif // QT_NO_FREETYPE + +// defined in qfontdatabase_x11.cpp +extern int qt_mib_for_xlfd_encoding(const char *encoding); +extern int qt_xlfd_encoding_id(const char *encoding); + +static inline XCharStruct *charStruct(XFontStruct *xfs, uint ch) +{ + XCharStruct *xcs = 0; + unsigned char r = ch>>8; + unsigned char c = ch&0xff; + if (xfs->per_char && + r >= xfs->min_byte1 && + r <= xfs->max_byte1 && + c >= xfs->min_char_or_byte2 && + c <= xfs->max_char_or_byte2) { + xcs = xfs->per_char + ((r - xfs->min_byte1) * + (xfs->max_char_or_byte2 - + xfs->min_char_or_byte2 + 1)) + + (c - xfs->min_char_or_byte2); + if (xcs->width == 0 && xcs->ascent == 0 && xcs->descent == 0) + xcs = 0; + } + return xcs; +} + +QFontEngineXLFD::QFontEngineXLFD(XFontStruct *fs, const QByteArray &name, int mib) + : _fs(fs), _name(name), _codec(0), _cmap(mib) +{ + if (_cmap) _codec = QTextCodec::codecForMib(_cmap); + + cache_cost = (((fs->max_byte1 - fs->min_byte1) * + (fs->max_char_or_byte2 - fs->min_char_or_byte2 + 1)) + + fs->max_char_or_byte2 - fs->min_char_or_byte2); + cache_cost = ((fs->max_bounds.ascent + fs->max_bounds.descent) * + (fs->max_bounds.width * cache_cost / 8)); + lbearing = SHRT_MIN; + rbearing = SHRT_MIN; + face_id.index = -1; + freetype = 0; + synth = 0; +} + +QFontEngineXLFD::~QFontEngineXLFD() +{ + XFreeFont(QX11Info::display(), _fs); + _fs = 0; +#ifndef QT_NO_FREETYPE + if (freetype) + freetype->release(face_id); +#endif +} + +bool QFontEngineXLFD::stringToCMap(const QChar *s, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + + // filter out surrogates, we can't handle them anyway with XLFD fonts + QVarLengthArray _s(len); + QChar *str = (QChar *)_s.data(); + for (int i = 0; i < len; ++i) { + if (i < len - 1 + && s[i].unicode() >= 0xd800 && s[i].unicode() < 0xdc00 + && s[i+1].unicode() >= 0xdc00 && s[i].unicode() < 0xe000) { + *str = QChar(); + ++i; + } else { + *str = s[i]; + } + ++str; + } + + len = str - (QChar *)_s.data(); + str = (QChar *)_s.data(); + + bool mirrored = flags & QTextEngine::RightToLeft; + if (_codec) { + bool haveNbsp = false; + for (int i = 0; i < len; i++) + if (str[i].unicode() == 0xa0) { + haveNbsp = true; + break; + } + + QVarLengthArray ch(len); + QChar *chars = (QChar *)ch.data(); + if (haveNbsp || mirrored) { + for (int i = 0; i < len; i++) + chars[i] = (str[i].unicode() == 0xa0 ? 0x20 : + (mirrored ? QChar::mirroredChar(str[i].unicode()) : str[i].unicode())); + } else { + for (int i = 0; i < len; i++) + chars[i] = str[i].unicode(); + } + QTextCodec::ConverterState state; + state.flags = QTextCodec::ConvertInvalidToNull; + QByteArray ba = _codec->fromUnicode(chars, len, &state); + if (ba.length() == 2*len) { + // double byte encoding + const uchar *data = (const uchar *)ba.constData(); + for (int i = 0; i < len; i++) { + glyphs->glyphs[i] = ((ushort)data[0] << 8) + data[1]; + data += 2; + } + } else { + const uchar *data = (const uchar *)ba.constData(); + for (int i = 0; i < len; i++) + glyphs->glyphs[i] = (ushort)data[i]; + } + } else { + int i = len; + const QChar *c = str + len; + if (mirrored) { + while (c != str) + glyphs->glyphs[--i] = (--c)->unicode() == 0xa0 ? 0x20 : QChar::mirroredChar(c->unicode()); + } else { + while (c != str) + glyphs->glyphs[--i] = (--c)->unicode() == 0xa0 ? 0x20 : c->unicode(); + } + } + *nglyphs = len; + glyphs->numGlyphs = len; + + if (!(flags & QTextEngine::GlyphIndicesOnly)) + recalcAdvances(glyphs, flags); + return true; +} + +void QFontEngineXLFD::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags /*flags*/) const +{ + int i = glyphs->numGlyphs; + XCharStruct *xcs; + // inlined for better performance + if (!_fs->per_char) { + xcs = &_fs->min_bounds; + while (i != 0) { + --i; + const unsigned char r = glyphs->glyphs[i] >> 8; + const unsigned char c = glyphs->glyphs[i] & 0xff; + if (r >= _fs->min_byte1 && + r <= _fs->max_byte1 && + c >= _fs->min_char_or_byte2 && + c <= _fs->max_char_or_byte2) { + glyphs->advances_x[i] = xcs->width; + } else { + glyphs->glyphs[i] = 0; + } + } + } + else if (!_fs->max_byte1) { + XCharStruct *base = _fs->per_char - _fs->min_char_or_byte2; + while (i != 0) { + unsigned int gl = glyphs->glyphs[--i]; + xcs = (gl >= _fs->min_char_or_byte2 && gl <= _fs->max_char_or_byte2) ? + base + gl : 0; + if (!xcs || (!xcs->width && !xcs->ascent && !xcs->descent)) { + glyphs->glyphs[i] = 0; + } else { + glyphs->advances_x[i] = xcs->width; + } + } + } + else { + while (i != 0) { + xcs = charStruct(_fs, glyphs->glyphs[--i]); + if (!xcs) { + glyphs->glyphs[i] = 0; + } else { + glyphs->advances_x[i] = xcs->width; + } + } + } +} + +glyph_metrics_t QFontEngineXLFD::boundingBox(const QGlyphLayout &glyphs) +{ + int i; + + glyph_metrics_t overall; + // initialize with line height, we get the same behaviour on all platforms + overall.y = -ascent(); + overall.height = ascent() + descent() + 1; + QFixed ymax; + QFixed xmax; + for (i = 0; i < glyphs.numGlyphs; i++) { + XCharStruct *xcs = charStruct(_fs, glyphs.glyphs[i]); + if (xcs) { + QFixed x = overall.xoff + glyphs.offsets[i].x + xcs->lbearing; + QFixed y = overall.yoff + glyphs.offsets[i].y - xcs->ascent; + overall.x = qMin(overall.x, x); + overall.y = qMin(overall.y, y); + // XCharStruct::rbearing is defined as distance from left edge to rightmost pixel + xmax = qMax(xmax, overall.xoff + glyphs.offsets[i].x + xcs->rbearing); + ymax = qMax(ymax, y + xcs->ascent + xcs->descent); + overall.xoff += glyphs.advances_x[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + } else { + QFixed size = _fs->ascent; + overall.x = qMin(overall.x, overall.xoff); + overall.y = qMin(overall.y, overall.yoff - size); + ymax = qMax(ymax, overall.yoff); + overall.xoff += size; + xmax = qMax(xmax, overall.xoff); + } + } + overall.height = qMax(overall.height, ymax - overall.y); + overall.width = xmax - overall.x; + + return overall; +} + +glyph_metrics_t QFontEngineXLFD::boundingBox(glyph_t glyph) +{ + glyph_metrics_t gm; + XCharStruct *xcs = charStruct(_fs, glyph); + if (xcs) { + // XCharStruct::rbearing is defined as distance from left edge to rightmost pixel + // XCharStruct::width is defined as the advance + gm = glyph_metrics_t(xcs->lbearing, -xcs->ascent, xcs->rbearing- xcs->lbearing, xcs->ascent + xcs->descent, + xcs->width, 0); + } else { + QFixed size = ascent(); + gm = glyph_metrics_t(0, size, size, size, size, 0); + } + return gm; +} + +QFixed QFontEngineXLFD::ascent() const +{ + return _fs->ascent; +} + +QFixed QFontEngineXLFD::descent() const +{ + return (_fs->descent-1); +} + +QFixed QFontEngineXLFD::leading() const +{ + QFixed l = QFixed(qMin(_fs->ascent, _fs->max_bounds.ascent) + + qMin(_fs->descent, _fs->max_bounds.descent)) * QFixed::fromReal(0.15); + return l.ceil(); +} + +qreal QFontEngineXLFD::maxCharWidth() const +{ + return _fs->max_bounds.width; +} + + +// Loads the font for the specified script +static inline int maxIndex(XFontStruct *f) { + return (((f->max_byte1 - f->min_byte1) * + (f->max_char_or_byte2 - f->min_char_or_byte2 + 1)) + + f->max_char_or_byte2 - f->min_char_or_byte2); +} + +qreal QFontEngineXLFD::minLeftBearing() const +{ + if (lbearing == SHRT_MIN) { + if (_fs->per_char) { + XCharStruct *cs = _fs->per_char; + int nc = maxIndex(_fs) + 1; + int mx = cs->lbearing; + + for (int c = 1; c < nc; c++) { + // ignore the bearings for characters whose ink is + // completely outside the normal bounding box + if ((cs[c].lbearing <= 0 && cs[c].rbearing <= 0) || + (cs[c].lbearing >= cs[c].width && cs[c].rbearing >= cs[c].width)) + continue; + + int nmx = cs[c].lbearing; + + if (nmx < mx) + mx = nmx; + } + + ((QFontEngineXLFD *)this)->lbearing = mx; + } else + ((QFontEngineXLFD *)this)->lbearing = _fs->min_bounds.lbearing; + } + return lbearing; +} + +qreal QFontEngineXLFD::minRightBearing() const +{ + if (rbearing == SHRT_MIN) { + if (_fs->per_char) { + XCharStruct *cs = _fs->per_char; + int nc = maxIndex(_fs) + 1; + int mx = cs->rbearing; + + for (int c = 1; c < nc; c++) { + // ignore the bearings for characters whose ink is + // completely outside the normal bounding box + if ((cs[c].lbearing <= 0 && cs[c].rbearing <= 0) || + (cs[c].lbearing >= cs[c].width && cs[c].rbearing >= cs[c].width)) + continue; + + int nmx = cs[c].rbearing; + + if (nmx < mx) + mx = nmx; + } + + ((QFontEngineXLFD *)this)->rbearing = mx; + } else + ((QFontEngineXLFD *)this)->rbearing = _fs->min_bounds.rbearing; + } + return rbearing; +} + +const char *QFontEngineXLFD::name() const +{ + return _name; +} + +bool QFontEngineXLFD::canRender(const QChar *string, int len) +{ + QVarLengthGlyphLayoutArray glyphs(len); + int nglyphs = len; + if (stringToCMap(string, len, &glyphs, &nglyphs, 0) == false) { + glyphs.resize(nglyphs); + stringToCMap(string, len, &glyphs, &nglyphs, 0); + } + + bool allExist = true; + for (int i = 0; i < nglyphs; i++) { + if (!glyphs.glyphs[i] || !charStruct(_fs, glyphs.glyphs[i])) { + allExist = false; + break; + } + } + + return allExist; +} + +QBitmap QFontEngineXLFD::bitmapForGlyphs(const QGlyphLayout &glyphs, const glyph_metrics_t &metrics, QTextItem::RenderFlags flags) +{ + int w = metrics.width.toInt(); + int h = metrics.height.toInt(); + if (w <= 0 || h <= 0) + return QBitmap(); + + QPixmapData *data = new QX11PixmapData(QPixmapData::BitmapType); + data->resize(w, h); + QPixmap bm(data); + QPainter p(&bm); + p.fillRect(0, 0, w, h, Qt::color0); + p.setPen(Qt::color1); + + QTextItemInt item; + item.flags = flags; + item.ascent = -metrics.y; + item.descent = metrics.height - item.ascent; + item.width = metrics.width; + item.chars = 0; + item.num_chars = 0; + item.logClusters = 0; + item.glyphs = glyphs; + item.fontEngine = this; + item.f = 0; + + p.drawTextItem(QPointF(-metrics.x.toReal(), item.ascent.toReal()), item); + p.end(); + + return QBitmap(bm); +} + +void QFontEngineXLFD::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + // cannot use QFontEngine::addBitmapFontToPath(), since we don't + // have direct access to the glyph bitmaps, so we have to draw + // onto a QBitmap, then convert to QImage, then to path + glyph_metrics_t metrics = boundingBox(glyphs); + + QImage image = bitmapForGlyphs(glyphs, metrics, flags).toImage(); + if (image.isNull()) + return; + + image = image.convertToFormat(QImage::Format_Mono); + const uchar *image_data = image.bits(); + uint bpl = image.bytesPerLine(); + // from qfontengine.cpp + extern void qt_addBitmapToPath(qreal x0, qreal y0, const uchar *image_data, + int bpl, int w, int h, QPainterPath *path); + qt_addBitmapToPath(x, y + metrics.y.toReal(), image_data, bpl, image.width(), image.height(), path); +} + +QFontEngine::FaceId QFontEngineXLFD::faceId() const +{ +#ifndef QT_NO_FREETYPE + if (face_id.index == -1) { + face_id = fontFile(_name, &freetype, &synth); + if (_codec) + face_id.encoding = _codec->mibEnum(); + if (freetype) { + const_cast(this)->fsType = freetype->fsType(); + } else { + face_id.index = 0; + face_id.filename = '-' + QFontEngine::properties().postscriptName; + } + } +#endif + + return face_id; +} + +QFontEngine::Properties QFontEngineXLFD::properties() const +{ + if (face_id.index == -1) + (void)faceId(); + +#ifndef QT_NO_FREETYPE + if (freetype) + return freetype->properties(); +#endif + return QFontEngine::properties(); +} + +void QFontEngineXLFD::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) +{ + if (face_id.index == -1) + (void)faceId(); +#ifndef QT_NO_FREETYPE + if (!freetype) +#endif + { + QFontEngine::getUnscaledGlyph(glyph, path, metrics); + return; + } + +#ifndef QT_NO_FREETYPE + freetype->lock(); + + FT_Face face = freetype->face; + FT_Set_Char_Size(face, face->units_per_EM << 6, face->units_per_EM << 6, 0, 0); + freetype->xsize = face->units_per_EM << 6; + freetype->ysize = face->units_per_EM << 6; + FT_Set_Transform(face, 0, 0); + glyph = glyphIndexToFreetypeGlyphIndex(glyph); + FT_Load_Glyph(face, glyph, FT_LOAD_NO_BITMAP); + + int left = face->glyph->metrics.horiBearingX; + int right = face->glyph->metrics.horiBearingX + face->glyph->metrics.width; + int top = face->glyph->metrics.horiBearingY; + int bottom = face->glyph->metrics.horiBearingY - face->glyph->metrics.height; + + QFixedPoint p; + p.x = 0; + p.y = 0; + metrics->width = QFixed::fromFixed(right-left); + metrics->height = QFixed::fromFixed(top-bottom); + metrics->x = QFixed::fromFixed(left); + metrics->y = QFixed::fromFixed(-top); + metrics->xoff = QFixed::fromFixed(face->glyph->advance.x); + + if (!FT_IS_SCALABLE(freetype->face)) + QFreetypeFace::addBitmapToPath(face->glyph, p, path); + else + QFreetypeFace::addGlyphToPath(face, face->glyph, p, path, face->units_per_EM << 6, face->units_per_EM << 6); + + FT_Set_Transform(face, &freetype->matrix, 0); + freetype->unlock(); +#endif // QT_NO_FREETYPE +} + + +bool QFontEngineXLFD::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ +#ifndef QT_NO_FREETYPE + if (face_id.index == -1) + (void)faceId(); + if (!freetype) + return false; + return freetype->getSfntTable(tag, buffer, length); +#else + Q_UNUSED(tag); + Q_UNUSED(buffer); + Q_UNUSED(length); + return false; +#endif +} + +int QFontEngineXLFD::synthesized() const +{ + return synth; +} + +QImage QFontEngineXLFD::alphaMapForGlyph(glyph_t glyph) +{ + glyph_metrics_t metrics = boundingBox(glyph); + +/* + printf("a) w=%.2f, h=%.2f, xoff=%.2f, yoff=%.2f, x=%.2f, y=%.2f\n", + metrics.width.toReal(), + metrics.height.toReal(), + metrics.xoff.toReal(), + metrics.yoff.toReal(), + metrics.x.toReal(), + metrics.y.toReal()); +*/ + + QGlyphLayoutArray<1> glyphs; + glyphs.glyphs[0] = glyph; + + QImage image = bitmapForGlyphs(glyphs, metrics).toImage(); +//image.save(QString::fromLatin1("x11cache-%1.png").arg((int)glyph)); + + image = image.convertToFormat(QImage::Format_Indexed8); + QVector colors(256); + for (int i = 0; i < 256; ++i) + colors[i] = qRgba(0, 0, 0, i); + image.setColorTable(colors); + + int width = image.width(); + int height = image.height(); + for (int y = 0; y < height; ++y) { + uchar *bits = image.scanLine(y); + for (int x = 0; x < width; ++x) + bits[x] = ~(bits[x]-1); + } + + return image; +} + +#ifndef QT_NO_FREETYPE + +FT_Face QFontEngineXLFD::non_locked_face() const +{ + return freetype ? freetype->face : 0; +} + +uint QFontEngineXLFD::toUnicode(glyph_t g) const +{ + if (_codec) { + QTextCodec::ConverterState state; + state.flags = QTextCodec::ConvertInvalidToNull; + uchar data[2]; + int l = 1; + if (g > 255) { + data[0] = (g >> 8); + data[1] = (g & 255); + l = 2; + } else { + data[0] = g; + } + QString s = _codec->toUnicode((char *)data, l, &state); + Q_ASSERT(s.length() == 1); + g = s.at(0).unicode(); + } + return g; +} + +glyph_t QFontEngineXLFD::glyphIndexToFreetypeGlyphIndex(glyph_t g) const +{ + return FT_Get_Char_Index(freetype->face, toUnicode(g)); +} +#endif + +#ifndef QT_NO_FONTCONFIG + +// ------------------------------------------------------------------ +// Multi FT engine +// ------------------------------------------------------------------ + +static QFontEngine *engineForPattern(FcPattern *pattern, const QFontDef &request, + int screen) +{ + FcResult res; + FcPattern *match = FcFontMatch(0, pattern, &res); + QFontEngineX11FT *engine = new QFontEngineX11FT(match, request, screen); + if (!engine->invalid()) + return engine; + + delete engine; + QFontEngine *fe = new QFontEngineBox(request.pixelSize); + fe->fontDef = request; + return fe; +} + +QFontEngineMultiFT::QFontEngineMultiFT(QFontEngine *fe, FcPattern *matchedPattern, FcPattern *p, int s, const QFontDef &req) + : QFontEngineMulti(2), request(req), pattern(p), firstEnginePattern(matchedPattern), fontSet(0), screen(s) +{ + + engines[0] = fe; + engines.at(0)->ref.ref(); + fontDef = engines[0]->fontDef; + cache_cost = 100; + firstFontIndex = 1; +} + +QFontEngineMultiFT::~QFontEngineMultiFT() +{ + extern QMutex *qt_fontdatabase_mutex(); + QMutexLocker locker(qt_fontdatabase_mutex()); + + FcPatternDestroy(pattern); + if (firstEnginePattern) + FcPatternDestroy(firstEnginePattern); + if (fontSet) + FcFontSetDestroy(fontSet); +} + + +void QFontEngineMultiFT::loadEngine(int at) +{ + extern QMutex *qt_fontdatabase_mutex(); + QMutexLocker locker(qt_fontdatabase_mutex()); + + extern void qt_addPatternProps(FcPattern *pattern, int screen, int script, + const QFontDef &request); + extern QFontDef qt_FcPatternToQFontDef(FcPattern *pattern, const QFontDef &); + extern FcFontSet *qt_fontSetForPattern(FcPattern *pattern, const QFontDef &request); + + Q_ASSERT(at > 0); + if (!fontSet) { + fontSet = qt_fontSetForPattern(pattern, request); + + // it may happen that the fontset of fallbacks consists of only one font. In this case we + // have to fall back to the box fontengine as we cannot render the glyph. + if (fontSet->nfont == 1 && at == 1 && engines.size() == 2) { + Q_ASSERT(engines.at(at) == 0); + QFontEngine *fe = new QFontEngineBox(request.pixelSize); + fe->fontDef = request; + engines[at] = fe; + return; + } + + if (firstEnginePattern) { + + if (!FcPatternEqual(firstEnginePattern, fontSet->fonts[0])) + firstFontIndex = 0; + + FcPatternDestroy(firstEnginePattern); + firstEnginePattern = 0; + } + + engines.resize(fontSet->nfont + 1 - firstFontIndex); + } + Q_ASSERT(at < engines.size()); + Q_ASSERT(engines.at(at) == 0); + + FcPattern *pattern = FcPatternDuplicate(fontSet->fonts[at + firstFontIndex - 1]); + qt_addPatternProps(pattern, screen, QUnicodeTables::Common, request); + + QFontDef fontDef = qt_FcPatternToQFontDef(pattern, this->request); + + // note: we use -1 for the script to make sure that we keep real + // FT engines separate from Multi engines in the font cache + QFontCache::Key key(fontDef, -1, screen); + QFontEngine *fontEngine = QFontCache::instance()->findEngine(key); + if (!fontEngine) { + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + fontEngine = engineForPattern(pattern, request, screen); + QFontCache::instance()->insertEngine(key, fontEngine); + } + FcPatternDestroy(pattern); + fontEngine->ref.ref(); + engines[at] = fontEngine; +} + +// ------------------------------------------------------------------ +// X11 FT engine +// ------------------------------------------------------------------ + + + +Q_GUI_EXPORT void qt_x11ft_convert_pattern(FcPattern *pattern, QByteArray *file_name, int *index, bool *antialias) +{ + FcChar8 *fileName; + FcPatternGetString(pattern, FC_FILE, 0, &fileName); + *file_name = (const char *)fileName; + if (!FcPatternGetInteger(pattern, FC_INDEX, 0, index)) + index = 0; + FcBool b; + if (FcPatternGetBool(pattern, FC_ANTIALIAS, 0, &b) == FcResultMatch) + *antialias = b; +} + + +QFontEngineX11FT::QFontEngineX11FT(FcPattern *pattern, const QFontDef &fd, int screen) + : QFontEngineFT(fd) +{ +// FcPatternPrint(pattern); + + bool antialias = X11->fc_antialias; + QByteArray file_name; + int face_index; + qt_x11ft_convert_pattern(pattern, &file_name, &face_index, &antialias); + QFontEngine::FaceId face_id; + face_id.filename = file_name; + face_id.index = face_index; + + canUploadGlyphsToServer = QApplication::testAttribute(Qt::AA_X11InitThreads) || (qApp->thread() == QThread::currentThread()); + + subpixelType = Subpixel_None; + if (antialias) { + int subpixel = X11->display ? X11->screens[screen].subpixel : FC_RGBA_UNKNOWN; + if (subpixel == FC_RGBA_UNKNOWN) + (void) FcPatternGetInteger(pattern, FC_RGBA, 0, &subpixel); + if (!antialias || subpixel == FC_RGBA_UNKNOWN) + subpixel = FC_RGBA_NONE; + + switch (subpixel) { + case FC_RGBA_NONE: subpixelType = Subpixel_None; break; + case FC_RGBA_RGB: subpixelType = Subpixel_RGB; break; + case FC_RGBA_BGR: subpixelType = Subpixel_BGR; break; + case FC_RGBA_VRGB: subpixelType = Subpixel_VRGB; break; + case FC_RGBA_VBGR: subpixelType = Subpixel_VBGR; break; + default: break; + } + } + + if (fd.hintingPreference != QFont::PreferDefaultHinting) { + switch (fd.hintingPreference) { + case QFont::PreferNoHinting: + default_hint_style = HintNone; + break; + case QFont::PreferVerticalHinting: + default_hint_style = HintLight; + break; + case QFont::PreferFullHinting: + default: + default_hint_style = HintFull; + break; + } + } +#ifdef FC_HINT_STYLE + else { + int hint_style = 0; + // Try to use Xft.hintstyle from XDefaults first if running in GNOME, to match + // the behavior of cairo + if (X11->fc_hint_style > -1 && X11->desktopEnvironment == DE_GNOME) + hint_style = X11->fc_hint_style; + else if (FcPatternGetInteger (pattern, FC_HINT_STYLE, 0, &hint_style) == FcResultNoMatch + && X11->fc_hint_style > -1) + hint_style = X11->fc_hint_style; + + switch (hint_style) { + case FC_HINT_NONE: + default_hint_style = HintNone; + break; + case FC_HINT_SLIGHT: + default_hint_style = HintLight; + break; + case FC_HINT_MEDIUM: + default_hint_style = HintMedium; + break; + default: + default_hint_style = HintFull; + break; + } + } +#endif + +#if defined(FC_AUTOHINT) && defined(FT_LOAD_FORCE_AUTOHINT) + { + bool autohint = false; + + FcBool b; + if (FcPatternGetBool(pattern, FC_AUTOHINT, 0, &b) == FcResultMatch) + autohint = b; + + if (autohint) + default_load_flags |= FT_LOAD_FORCE_AUTOHINT; + } +#endif + +#if defined(FC_LCD_FILTER) && defined(FT_LCD_FILTER_H) + { + int filter = FC_LCD_FILTER_NONE; + if (FcPatternGetInteger(pattern, FC_LCD_FILTER, 0, &filter) == FcResultMatch) { + switch (filter) { + case FC_LCD_FILTER_NONE: + lcdFilterType = FT_LCD_FILTER_NONE; + break; + case FC_LCD_FILTER_DEFAULT: + lcdFilterType = FT_LCD_FILTER_DEFAULT; + break; + case FC_LCD_FILTER_LIGHT: + lcdFilterType = FT_LCD_FILTER_LIGHT; + break; + case FC_LCD_FILTER_LEGACY: + lcdFilterType = FT_LCD_FILTER_LEGACY; + break; + default: + // new unknown lcd filter type?! + break; + } + } + } +#endif + +#ifdef FC_EMBEDDED_BITMAP + { + FcBool b; + if (FcPatternGetBool(pattern, FC_EMBEDDED_BITMAP, 0, &b) == FcResultMatch) + embeddedbitmap = b; + } +#endif + + GlyphFormat defaultFormat = Format_None; + +#ifndef QT_NO_XRENDER + if (X11->use_xrender) { + int format = PictStandardA8; + if (!antialias) + format = PictStandardA1; + else if (subpixelType == Subpixel_RGB + || subpixelType == Subpixel_BGR + || subpixelType == Subpixel_VRGB + || subpixelType == Subpixel_VBGR) + format = PictStandardARGB32; + xglyph_format = format; + + if (subpixelType != QFontEngineFT::Subpixel_None) + defaultFormat = Format_A32; + else if (antialias) + defaultFormat = Format_A8; + else + defaultFormat = Format_Mono; + } +#endif + + if (!init(face_id, antialias, defaultFormat)) { + FcPatternDestroy(pattern); + return; + } + + if (!freetype->charset) { + FcCharSet *cs; + FcPatternGetCharSet (pattern, FC_CHARSET, 0, &cs); + freetype->charset = FcCharSetCopy(cs); + } + FcPatternDestroy(pattern); +} + +QFontEngineX11FT::~QFontEngineX11FT() +{ + freeGlyphSets(); +} + +unsigned long QFontEngineX11FT::allocateServerGlyphSet() +{ +#ifndef QT_NO_XRENDER + if (!canUploadGlyphsToServer || !X11->use_xrender) + return 0; + return XRenderCreateGlyphSet(X11->display, XRenderFindStandardFormat(X11->display, xglyph_format)); +#else + return 0; +#endif +} + +void QFontEngineX11FT::freeServerGlyphSet(unsigned long id) +{ +#ifndef QT_NO_XRENDER + if (!id) + return; + XRenderFreeGlyphSet(X11->display, id); +#endif +} + +bool QFontEngineX11FT::uploadGlyphToServer(QGlyphSet *set, uint glyphid, Glyph *g, GlyphInfo *info, int glyphDataSize) const +{ +#ifndef QT_NO_XRENDER + if (!canUploadGlyphsToServer) + return false; + if (g->format == Format_Mono) { + /* + * swap bit order around; FreeType is always MSBFirst + */ + if (BitmapBitOrder(X11->display) != MSBFirst) { + unsigned char *line = g->data; + int i = glyphDataSize; + while (i--) { + unsigned char c; + c = *line; + c = ((c << 1) & 0xaa) | ((c >> 1) & 0x55); + c = ((c << 2) & 0xcc) | ((c >> 2) & 0x33); + c = ((c << 4) & 0xf0) | ((c >> 4) & 0x0f); + *line++ = c; + } + } + } + + ::Glyph xglyph = glyphid; + XRenderAddGlyphs (X11->display, set->id, &xglyph, info, 1, (const char *)g->data, glyphDataSize); + delete [] g->data; + g->data = 0; + g->format = Format_None; + g->uploadedToServer = true; + return true; +#else + return false; +#endif +} + +#endif // QT_NO_FONTCONFIG + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_x11_p.h b/src/gui/text/qfontengine_x11_p.h new file mode 100644 index 0000000000..ad68fac167 --- /dev/null +++ b/src/gui/text/qfontengine_x11_p.h @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTENGINE_X11_P_H +#define QFONTENGINE_X11_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// +#include + +#include + +QT_BEGIN_NAMESPACE + +class QFreetypeFace; + +// -------------------------------------------------------------------------- + +class QFontEngineMultiXLFD : public QFontEngineMulti +{ +public: + QFontEngineMultiXLFD(const QFontDef &r, const QList &l, int s); + ~QFontEngineMultiXLFD(); + + void loadEngine(int at); + +private: + QList encodings; + int screen; + QFontDef request; +}; + +/** + * \internal The font engine for X Logical Font Description (XLFD) fonts, which is for X11 systems without freetype. + */ +class QFontEngineXLFD : public QFontEngine +{ +public: + QFontEngineXLFD(XFontStruct *f, const QByteArray &name, int mib); + ~QFontEngineXLFD(); + + virtual QFontEngine::FaceId faceId() const; + QFontEngine::Properties properties() const; + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + virtual bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; + virtual int synthesized() const; + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags); + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual qreal maxCharWidth() const; + virtual qreal minLeftBearing() const; + virtual qreal minRightBearing() const; + virtual QImage alphaMapForGlyph(glyph_t); + + virtual inline Type type() const + { return QFontEngine::XLFD; } + + virtual bool canRender(const QChar *string, int len); + virtual const char *name() const; + + inline XFontStruct *fontStruct() const + { return _fs; } + +#ifndef QT_NO_FREETYPE + FT_Face non_locked_face() const; + glyph_t glyphIndexToFreetypeGlyphIndex(glyph_t g) const; +#endif + uint toUnicode(glyph_t g) const; + +private: + QBitmap bitmapForGlyphs(const QGlyphLayout &glyphs, const glyph_metrics_t &metrics, QTextItem::RenderFlags flags = 0); + + XFontStruct *_fs; + QByteArray _name; + QTextCodec *_codec; + int _cmap; + int lbearing, rbearing; + mutable QFontEngine::FaceId face_id; + mutable QFreetypeFace *freetype; + mutable int synth; +}; + +#ifndef QT_NO_FONTCONFIG + +class Q_GUI_EXPORT QFontEngineMultiFT : public QFontEngineMulti +{ +public: + QFontEngineMultiFT(QFontEngine *fe, FcPattern *firstEnginePattern, FcPattern *p, int s, const QFontDef &request); + ~QFontEngineMultiFT(); + + void loadEngine(int at); + +private: + QFontDef request; + FcPattern *pattern; + FcPattern *firstEnginePattern; + FcFontSet *fontSet; + int screen; + int firstFontIndex; // first font in fontset +}; + +class Q_GUI_EXPORT QFontEngineX11FT : public QFontEngineFT +{ +public: + explicit QFontEngineX11FT(const QFontDef &fontDef) : QFontEngineFT(fontDef) {} + explicit QFontEngineX11FT(FcPattern *pattern, const QFontDef &fd, int screen); + ~QFontEngineX11FT(); + +#ifndef QT_NO_XRENDER + int xglyph_format; +#endif + +protected: + virtual bool uploadGlyphToServer(QGlyphSet *set, uint glyphid, Glyph *g, GlyphInfo *info, int glyphDataSize) const; + virtual unsigned long allocateServerGlyphSet(); + virtual void freeServerGlyphSet(unsigned long id); +}; + +#endif // QT_NO_FONTCONFIG + +QT_END_NAMESPACE + +#endif // QFONTENGINE_X11_P_H diff --git a/src/gui/text/qfontenginedirectwrite.cpp b/src/gui/text/qfontenginedirectwrite.cpp new file mode 100644 index 0000000000..f0a3644865 --- /dev/null +++ b/src/gui/text/qfontenginedirectwrite.cpp @@ -0,0 +1,635 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_DIRECTWRITE + +#include "qfontenginedirectwrite_p.h" + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +// Convert from design units to logical pixels +#define DESIGN_TO_LOGICAL(DESIGN_UNIT_VALUE) \ + QFixed::fromReal((qreal(DESIGN_UNIT_VALUE) / qreal(m_unitsPerEm)) * fontDef.pixelSize) + +namespace { + + class GeometrySink: public IDWriteGeometrySink + { + public: + GeometrySink(QPainterPath *path) : m_path(path), m_refCount(0) + { + Q_ASSERT(m_path != 0); + } + + IFACEMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *beziers, UINT bezierCount); + IFACEMETHOD_(void, AddLines)(const D2D1_POINT_2F *points, UINT pointCount); + IFACEMETHOD_(void, BeginFigure)(D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN figureBegin); + IFACEMETHOD(Close)(); + IFACEMETHOD_(void, EndFigure)(D2D1_FIGURE_END figureEnd); + IFACEMETHOD_(void, SetFillMode)(D2D1_FILL_MODE fillMode); + IFACEMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT vertexFlags); + + IFACEMETHOD_(unsigned long, AddRef)(); + IFACEMETHOD_(unsigned long, Release)(); + IFACEMETHOD(QueryInterface)(IID const &riid, void **ppvObject); + + private: + inline static QPointF fromD2D1_POINT_2F(const D2D1_POINT_2F &inp) + { + return QPointF(inp.x, inp.y); + } + + unsigned long m_refCount; + QPointF m_startPoint; + QPainterPath *m_path; + }; + + void GeometrySink::AddBeziers(const D2D1_BEZIER_SEGMENT *beziers, + UINT bezierCount) + { + for (uint i=0; icubicTo(c1, c2, p2); + } + } + + void GeometrySink::AddLines(const D2D1_POINT_2F *points, UINT pointsCount) + { + for (uint i=0; ilineTo(fromD2D1_POINT_2F(points[i])); + } + + void GeometrySink::BeginFigure(D2D1_POINT_2F startPoint, + D2D1_FIGURE_BEGIN /*figureBegin*/) + { + m_startPoint = fromD2D1_POINT_2F(startPoint); + m_path->moveTo(m_startPoint); + } + + IFACEMETHODIMP GeometrySink::Close() + { + return E_NOTIMPL; + } + + void GeometrySink::EndFigure(D2D1_FIGURE_END figureEnd) + { + if (figureEnd == D2D1_FIGURE_END_CLOSED) + m_path->closeSubpath(); + } + + void GeometrySink::SetFillMode(D2D1_FILL_MODE fillMode) + { + m_path->setFillRule(fillMode == D2D1_FILL_MODE_ALTERNATE + ? Qt::OddEvenFill + : Qt::WindingFill); + } + + void GeometrySink::SetSegmentFlags(D2D1_PATH_SEGMENT /*vertexFlags*/) + { + /* Not implemented */ + } + + IFACEMETHODIMP_(unsigned long) GeometrySink::AddRef() + { + return InterlockedIncrement(&m_refCount); + } + + IFACEMETHODIMP_(unsigned long) GeometrySink::Release() + { + unsigned long newCount = InterlockedDecrement(&m_refCount); + if (newCount == 0) + { + delete this; + return 0; + } + + return newCount; + } + + IFACEMETHODIMP GeometrySink::QueryInterface(IID const &riid, void **ppvObject) + { + if (__uuidof(IDWriteGeometrySink) == riid) { + *ppvObject = this; + } else if (__uuidof(IUnknown) == riid) { + *ppvObject = this; + } else { + *ppvObject = NULL; + return E_FAIL; + } + + AddRef(); + return S_OK; + } + +} + +QFontEngineDirectWrite::QFontEngineDirectWrite(IDWriteFactory *directWriteFactory, + IDWriteFontFace *directWriteFontFace, + qreal pixelSize) + : m_directWriteFontFace(directWriteFontFace) + , m_directWriteFactory(directWriteFactory) + , m_directWriteBitmapRenderTarget(0) + , m_lineThickness(-1) + , m_unitsPerEm(-1) + , m_ascent(-1) + , m_descent(-1) + , m_xHeight(-1) + , m_lineGap(-1) +{ + m_directWriteFactory->AddRef(); + m_directWriteFontFace->AddRef(); + + fontDef.pixelSize = pixelSize; + collectMetrics(); +} + +QFontEngineDirectWrite::~QFontEngineDirectWrite() +{ + m_directWriteFactory->Release(); + m_directWriteFontFace->Release(); + + if (m_directWriteBitmapRenderTarget != 0) + m_directWriteBitmapRenderTarget->Release(); +} + +void QFontEngineDirectWrite::collectMetrics() +{ + if (m_directWriteFontFace != 0) { + DWRITE_FONT_METRICS metrics; + + m_directWriteFontFace->GetMetrics(&metrics); + m_unitsPerEm = metrics.designUnitsPerEm; + + m_lineThickness = DESIGN_TO_LOGICAL(metrics.underlineThickness); + m_ascent = DESIGN_TO_LOGICAL(metrics.ascent); + m_descent = DESIGN_TO_LOGICAL(metrics.descent); + m_xHeight = DESIGN_TO_LOGICAL(metrics.xHeight); + m_lineGap = DESIGN_TO_LOGICAL(metrics.lineGap); + } +} + +QFixed QFontEngineDirectWrite::lineThickness() const +{ + if (m_lineThickness > 0) + return m_lineThickness; + else + return QFontEngine::lineThickness(); +} + +bool QFontEngineDirectWrite::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + if (m_directWriteFontFace) { + DWORD t = qbswap(tag); + + const void *tableData = 0; + void *tableContext = 0; + UINT32 tableSize; + BOOL exists; + HRESULT hr = m_directWriteFontFace->TryGetFontTable( + t, &tableData, &tableSize, &tableContext, &exists + ); + + if (SUCCEEDED(hr)) { + if (!exists) + return false; + + if (buffer == 0) { + *length = tableSize; + return true; + } else if (*length < tableSize) { + return false; + } + + qMemCopy(buffer, tableData, tableSize); + m_directWriteFontFace->ReleaseFontTable(tableContext); + + return true; + } else { + qErrnoWarning("QFontEngineDirectWrite::getSfntTableData: TryGetFontTable failed"); + } + } + + return false; +} + +QFixed QFontEngineDirectWrite::emSquareSize() const +{ + if (m_unitsPerEm > 0) + return m_unitsPerEm; + else + return QFontEngine::emSquareSize(); +} + +inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +bool QFontEngineDirectWrite::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, + int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (m_directWriteFontFace != 0) { + QVarLengthArray codePoints(len); + for (int i=0; i glyphIndices(len); + HRESULT hr = m_directWriteFontFace->GetGlyphIndicesW(codePoints.data(), + len, + glyphIndices.data()); + + if (SUCCEEDED(hr)) { + for (int i=0; iglyphs[i] = glyphIndices[i]; + + *nglyphs = len; + + if (!(flags & QTextEngine::GlyphIndicesOnly)) + recalcAdvances(glyphs, 0); + + return true; + } else { + qErrnoWarning("QFontEngineDirectWrite::stringToCMap: GetGlyphIndicesW failed"); + } + } + + return false; +} + +void QFontEngineDirectWrite::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const +{ + if (m_directWriteFontFace == 0) + return; + + QVarLengthArray glyphIndices(glyphs->numGlyphs); + + // ### Caching? + for(int i=0; inumGlyphs; i++) + glyphIndices[i] = UINT16(glyphs->glyphs[i]); + + QVarLengthArray glyphMetrics(glyphIndices.size()); + HRESULT hr = m_directWriteFontFace->GetDesignGlyphMetrics(glyphIndices.data(), + glyphIndices.size(), + glyphMetrics.data()); + if (SUCCEEDED(hr)) { + for (int i=0; inumGlyphs; ++i) { + glyphs->advances_x[i] = DESIGN_TO_LOGICAL(glyphMetrics[i].advanceWidth); + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + glyphs->advances_x[i] = glyphs->advances_x[i].round(); + glyphs->advances_y[i] = 0; + } + } else { + qErrnoWarning("QFontEngineDirectWrite::recalcAdvances: GetDesignGlyphMetrics failed"); + } +} + +void QFontEngineDirectWrite::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (m_directWriteFontFace == 0) + return; + + QVarLengthArray glyphIndices(nglyphs); + QVarLengthArray glyphOffsets(nglyphs); + QVarLengthArray glyphAdvances(nglyphs); + + for (int i=0; iGetGlyphRunOutline( + fontDef.pixelSize, + glyphIndices.data(), + glyphAdvances.data(), + glyphOffsets.data(), + nglyphs, + false, + flags & QTextItem::RightToLeft, + &geometrySink + ); + + if (FAILED(hr)) + qErrnoWarning("QFontEngineDirectWrite::addGlyphsToPath: GetGlyphRunOutline failed"); +} + +glyph_metrics_t QFontEngineDirectWrite::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs == 0) + return glyph_metrics_t(); + + bool round = fontDef.styleStrategy & QFont::ForceIntegerMetrics; + + QFixed w = 0; + for (int i = 0; i < glyphs.numGlyphs; ++i) { + w += round ? glyphs.effectiveAdvance(i).round() : glyphs.effectiveAdvance(i); + + } + + return glyph_metrics_t(0, -m_ascent, w - lastRightBearing(glyphs), m_ascent + m_descent, w, 0); +} + +glyph_metrics_t QFontEngineDirectWrite::boundingBox(glyph_t g) +{ + if (m_directWriteFontFace == 0) + return glyph_metrics_t(); + + UINT16 glyphIndex = g; + + DWRITE_GLYPH_METRICS glyphMetrics; + HRESULT hr = m_directWriteFontFace->GetDesignGlyphMetrics(&glyphIndex, 1, &glyphMetrics); + if (SUCCEEDED(hr)) { + QFixed advanceWidth = DESIGN_TO_LOGICAL(glyphMetrics.advanceWidth); + QFixed leftSideBearing = DESIGN_TO_LOGICAL(glyphMetrics.leftSideBearing); + QFixed rightSideBearing = DESIGN_TO_LOGICAL(glyphMetrics.rightSideBearing); + QFixed advanceHeight = DESIGN_TO_LOGICAL(glyphMetrics.advanceHeight); + QFixed verticalOriginY = DESIGN_TO_LOGICAL(glyphMetrics.verticalOriginY); + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + advanceWidth = advanceWidth.round(); + advanceHeight = advanceHeight.round(); + } + + QFixed width = advanceWidth - leftSideBearing - rightSideBearing; + + return glyph_metrics_t(-leftSideBearing, -verticalOriginY, + width, m_ascent + m_descent, + advanceWidth, advanceHeight); + } else { + qErrnoWarning("QFontEngineDirectWrite::boundingBox: GetDesignGlyphMetrics failed"); + } + + return glyph_metrics_t(); +} + +QFixed QFontEngineDirectWrite::ascent() const +{ + return fontDef.styleStrategy & QFont::ForceIntegerMetrics + ? m_ascent.round() + : m_ascent; +} + +QFixed QFontEngineDirectWrite::descent() const +{ + return fontDef.styleStrategy & QFont::ForceIntegerMetrics + ? (m_descent - 1).round() + : (m_descent - 1); +} + +QFixed QFontEngineDirectWrite::leading() const +{ + return fontDef.styleStrategy & QFont::ForceIntegerMetrics + ? m_lineGap.round() + : m_lineGap; +} + +QFixed QFontEngineDirectWrite::xHeight() const +{ + return fontDef.styleStrategy & QFont::ForceIntegerMetrics + ? m_xHeight.round() + : m_xHeight; +} + +qreal QFontEngineDirectWrite::maxCharWidth() const +{ + // ### + return 0; +} + +extern uint qt_pow_gamma[256]; + +QImage QFontEngineDirectWrite::alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition) +{ + QImage im = imageForGlyph(glyph, subPixelPosition, 0, QTransform()); + + QImage indexed(im.width(), im.height(), QImage::Format_Indexed8); + QVector colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; yCreateGlyphRunAnalysis( + &glyphRun, + 1.0f, + &transform, + DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC, + DWRITE_MEASURING_MODE_NATURAL, + 0.0, 0.0, + &glyphAnalysis + ); + + if (SUCCEEDED(hr)) { + RECT rect; + rect.left = 0; + rect.top = 0; + rect.right = width; + rect.bottom = height; + + int size = width * height * 3; + BYTE *alphaValues = new BYTE[size]; + qMemSet(alphaValues, size, 0); + + hr = glyphAnalysis->CreateAlphaTexture(DWRITE_TEXTURE_CLEARTYPE_3x1, + &rect, + alphaValues, + size); + + if (SUCCEEDED(hr)) { + QImage img(width, height, QImage::Format_RGB32); + img.fill(0xffffffff); + + for (int y=0; y(img.scanLine(y)); + BYTE *src = alphaValues + width * 3 * y; + + for (int x=0; xRelease(); + + return img; + } else { + delete[] alphaValues; + glyphAnalysis->Release(); + + qErrnoWarning("QFontEngineDirectWrite::imageForGlyph: CreateAlphaTexture failed"); + } + + } else { + qErrnoWarning("QFontEngineDirectWrite::imageForGlyph: CreateGlyphRunAnalysis failed"); + } + + return QImage(); +} + +QImage QFontEngineDirectWrite::alphaRGBMapForGlyph(glyph_t t, + QFixed subPixelPosition, + int margin, + const QTransform &xform) +{ + QImage mask = imageForGlyph(t, subPixelPosition, margin, xform); + return mask.depth() == 32 + ? mask + : mask.convertToFormat(QImage::Format_RGB32); +} + +const char *QFontEngineDirectWrite::name() const +{ + return 0; +} + +bool QFontEngineDirectWrite::canRender(const QChar *string, int len) +{ + QVarLengthArray codePoints(len); + int actualLength = 0; + for (int i=0; i glyphIndices(actualLength); + HRESULT hr = m_directWriteFontFace->GetGlyphIndices(codePoints.data(), actualLength, + glyphIndices.data()); + if (FAILED(hr)) { + qErrnoWarning(hr, "QFontEngineDirectWrite::canRender: GetGlyphIndices failed"); + return false; + } else { + for (int i=0; i +#include "private/qfont_p.h" + +#ifdef Q_WS_WIN +# include "QtCore/qt_windows.h" +#endif + +#ifdef Q_WS_MAC +# include "private/qt_mac_p.h" +# include "QtCore/qmap.h" +# include "QtCore/qcache.h" +# include "private/qcore_mac_p.h" +#endif + +QT_BEGIN_NAMESPACE + +class QFontEngineGlyphCache: public QSharedData +{ +public: + enum Type { + Raster_RGBMask, + Raster_A8, + Raster_Mono + }; + + QFontEngineGlyphCache(const QTransform &matrix, Type type) : m_transform(matrix), m_type(type) { } + + virtual ~QFontEngineGlyphCache() { } + + Type cacheType() const { return m_type; } + + QTransform m_transform; + QFontEngineGlyphCache::Type m_type; +}; +typedef QHash > GlyphPointerHash; +typedef QHash > GlyphIntHash; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/text/qfontinfo.h b/src/gui/text/qfontinfo.h new file mode 100644 index 0000000000..cc7db4acf4 --- /dev/null +++ b/src/gui/text/qfontinfo.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTINFO_H +#define QFONTINFO_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class Q_GUI_EXPORT QFontInfo +{ +public: + QFontInfo(const QFont &); + QFontInfo(const QFontInfo &); + ~QFontInfo(); + + QFontInfo &operator=(const QFontInfo &); + + QString family() const; + int pixelSize() const; + int pointSize() const; + qreal pointSizeF() const; + bool italic() const; + QFont::Style style() const; + int weight() const; + inline bool bold() const { return weight() > QFont::Normal; } + bool underline() const; + bool overline() const; + bool strikeOut() const; + bool fixedPitch() const; + QFont::StyleHint styleHint() const; + bool rawMode() const; + + bool exactMatch() const; + +private: + QExplicitlySharedDataPointer d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFONTINFO_H diff --git a/src/gui/text/qfontmetrics.cpp b/src/gui/text/qfontmetrics.cpp new file mode 100644 index 0000000000..2ec245e5da --- /dev/null +++ b/src/gui/text/qfontmetrics.cpp @@ -0,0 +1,1819 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qfont.h" +#include "qpaintdevice.h" +#include "qfontmetrics.h" + +#include "qfont_p.h" +#include "qfontengine_p.h" +#include + +#include + +#ifdef Q_WS_X11 +#include "qx11info_x11.h" +#endif + +QT_BEGIN_NAMESPACE + +#ifdef Q_WS_X11 +extern const QX11Info *qt_x11Info(const QPaintDevice *pd); +#endif + +extern void qt_format_text(const QFont& font, const QRectF &_r, + int tf, const QString &text, QRectF *brect, + int tabStops, int *tabArray, int tabArrayLen, + QPainter *painter); + +/***************************************************************************** + QFontMetrics member functions + *****************************************************************************/ + +/*! + \class QFontMetrics + \reentrant + + \brief The QFontMetrics class provides font metrics information. + + \ingroup painting + \ingroup shared + + QFontMetrics functions calculate the size of characters and + strings for a given font. There are three ways you can create a + QFontMetrics object: + + \list 1 + \o Calling the QFontMetrics constructor with a QFont creates a + font metrics object for a screen-compatible font, i.e. the font + cannot be a printer font. If the font is changed + later, the font metrics object is \e not updated. + + (Note: If you use a printer font the values returned may be + inaccurate. Printer fonts are not always accessible so the nearest + screen font is used if a printer font is supplied.) + + \o QWidget::fontMetrics() returns the font metrics for a widget's + font. This is equivalent to QFontMetrics(widget->font()). If the + widget's font is changed later, the font metrics object is \e not + updated. + + \o QPainter::fontMetrics() returns the font metrics for a + painter's current font. If the painter's font is changed later, the + font metrics object is \e not updated. + \endlist + + Once created, the object provides functions to access the + individual metrics of the font, its characters, and for strings + rendered in the font. + + There are several functions that operate on the font: ascent(), + descent(), height(), leading() and lineSpacing() return the basic + size properties of the font. The underlinePos(), overlinePos(), + strikeOutPos() and lineWidth() functions, return the properties of + the line that underlines, overlines or strikes out the + characters. These functions are all fast. + + There are also some functions that operate on the set of glyphs in + the font: minLeftBearing(), minRightBearing() and maxWidth(). + These are by necessity slow, and we recommend avoiding them if + possible. + + For each character, you can get its width(), leftBearing() and + rightBearing() and find out whether it is in the font using + inFont(). You can also treat the character as a string, and use + the string functions on it. + + The string functions include width(), to return the width of a + string in pixels (or points, for a printer), boundingRect(), to + return a rectangle large enough to contain the rendered string, + and size(), to return the size of that rectangle. + + Example: + \snippet doc/src/snippets/code/src_gui_text_qfontmetrics.cpp 0 + + \sa QFont, QFontInfo, QFontDatabase, QFontComboBox, {Character Map Example} +*/ + +/*! + \fn QRect QFontMetrics::boundingRect(int x, int y, int width, int height, + int flags, const QString &text, int tabStops, int *tabArray) const + \overload + + Returns the bounding rectangle for the given \a text within the + rectangle specified by the \a x and \a y coordinates, \a width, and + \a height. + + If Qt::TextExpandTabs is set in \a flags and \a tabArray is + non-null, it specifies a 0-terminated sequence of pixel-positions + for tabs; otherwise, if \a tabStops is non-zero, it is used as the + tab spacing (in pixels). +*/ + +/*! + Constructs a font metrics object for \a font. + + The font metrics will be compatible with the paintdevice used to + create \a font. + + The font metrics object holds the information for the font that is + passed in the constructor at the time it is created, and is not + updated if the font's attributes are changed later. + + Use QFontMetrics(const QFont &, QPaintDevice *) to get the font + metrics that are compatible with a certain paint device. +*/ +QFontMetrics::QFontMetrics(const QFont &font) + : d(font.d.data()) +{ +} + +/*! + Constructs a font metrics object for \a font and \a paintdevice. + + The font metrics will be compatible with the paintdevice passed. + If the \a paintdevice is 0, the metrics will be screen-compatible, + ie. the metrics you get if you use the font for drawing text on a + \link QWidget widgets\endlink or \link QPixmap pixmaps\endlink, + not on a QPicture or QPrinter. + + The font metrics object holds the information for the font that is + passed in the constructor at the time it is created, and is not + updated if the font's attributes are changed later. +*/ +QFontMetrics::QFontMetrics(const QFont &font, QPaintDevice *paintdevice) +{ + int dpi = paintdevice ? paintdevice->logicalDpiY() : qt_defaultDpi(); +#ifdef Q_WS_X11 + const QX11Info *info = qt_x11Info(paintdevice); + int screen = info ? info->screen() : 0; +#else + const int screen = 0; +#endif + if (font.d->dpi != dpi || font.d->screen != screen ) { + d = new QFontPrivate(*font.d); + d->dpi = dpi; + d->screen = screen; + } else { + d = font.d.data(); + } + +} + +/*! + Constructs a copy of \a fm. +*/ +QFontMetrics::QFontMetrics(const QFontMetrics &fm) + : d(fm.d.data()) +{ +} + +/*! + Destroys the font metrics object and frees all allocated + resources. +*/ +QFontMetrics::~QFontMetrics() +{ +} + +/*! + Assigns the font metrics \a fm. +*/ +QFontMetrics &QFontMetrics::operator=(const QFontMetrics &fm) +{ + d = fm.d.data(); + return *this; +} + +/*! + \overload + Returns true if \a other is equal to this object; otherwise + returns false. + + Two font metrics are considered equal if they were constructed + from the same QFont and the paint devices they were constructed + for are considered compatible. + + \sa operator!=() +*/ +bool QFontMetrics::operator ==(const QFontMetrics &other) const +{ + return d == other.d; +} + +/*! + Returns true if \a other is equal to this object; otherwise + returns false. + + Two font metrics are considered equal if they were constructed + from the same QFont and the paint devices they were constructed + for are considered compatible. + + \sa operator!=() +*/ +bool QFontMetrics::operator ==(const QFontMetrics &other) +{ + return d == other.d; +} + +/*! + \fn bool QFontMetrics::operator!=(const QFontMetrics &other) + + Returns true if \a other is not equal to this object; otherwise returns false. + + Two font metrics are considered equal if they were constructed + from the same QFont and the paint devices they were constructed + for are considered compatible. + + \sa operator==() +*/ + +/*! + \fn bool QFontMetrics::operator !=(const QFontMetrics &other) const + + Returns true if \a other is not equal to this object; otherwise returns false. + + Two font metrics are considered equal if they were constructed + from the same QFont and the paint devices they were constructed + for are considered compatible. + + \sa operator==() +*/ + +/*! + Returns the ascent of the font. + + The ascent of a font is the distance from the baseline to the + highest position characters extend to. In practice, some font + designers break this rule, e.g. when they put more than one accent + on top of a character, or to accommodate an unusual character in + an exotic language, so it is possible (though rare) that this + value will be too small. + + \sa descent() +*/ +int QFontMetrics::ascent() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->ascent()); +} + + +/*! + Returns the descent of the font. + + The descent is the distance from the base line to the lowest point + characters extend to. In practice, some font designers break this rule, + e.g. to accommodate an unusual character in an exotic language, so + it is possible (though rare) that this value will be too small. + + \sa ascent() +*/ +int QFontMetrics::descent() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->descent()); +} + +/*! + Returns the height of the font. + + This is always equal to ascent()+descent()+1 (the 1 is for the + base line). + + \sa leading(), lineSpacing() +*/ +int QFontMetrics::height() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->ascent()) + qRound(engine->descent()) + 1; +} + +/*! + Returns the leading of the font. + + This is the natural inter-line spacing. + + \sa height(), lineSpacing() +*/ +int QFontMetrics::leading() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->leading()); +} + +/*! + Returns the distance from one base line to the next. + + This value is always equal to leading()+height(). + + \sa height(), leading() +*/ +int QFontMetrics::lineSpacing() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->leading()) + qRound(engine->ascent()) + qRound(engine->descent()) + 1; +} + +/*! + Returns the minimum left bearing of the font. + + This is the smallest leftBearing(char) of all characters in the + font. + + Note that this function can be very slow if the font is large. + + \sa minRightBearing(), leftBearing() +*/ +int QFontMetrics::minLeftBearing() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->minLeftBearing()); +} + +/*! + Returns the minimum right bearing of the font. + + This is the smallest rightBearing(char) of all characters in the + font. + + Note that this function can be very slow if the font is large. + + \sa minLeftBearing(), rightBearing() +*/ +int QFontMetrics::minRightBearing() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->minRightBearing()); +} + +/*! + Returns the width of the widest character in the font. +*/ +int QFontMetrics::maxWidth() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->maxCharWidth()); +} + +/*! + Returns the 'x' height of the font. This is often but not always + the same as the height of the character 'x'. +*/ +int QFontMetrics::xHeight() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + if (d->capital == QFont::SmallCaps) + return qRound(d->smallCapsFontPrivate()->engineForScript(QUnicodeTables::Common)->ascent()); + return qRound(engine->xHeight()); +} + +/*! + \since 4.2 + + Returns the average width of glyphs in the font. +*/ +int QFontMetrics::averageCharWidth() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->averageCharWidth()); +} + +/*! + Returns true if character \a ch is a valid character in the font; + otherwise returns false. +*/ +bool QFontMetrics::inFont(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return false; + return engine->canRender(&ch, 1); +} + +/*! + Returns true if the character encoded in UCS-4/UTF-32 is a valid + character in the font; otherwise returns false. +*/ +bool QFontMetrics::inFontUcs4(uint ucs4) const +{ + const int script = QUnicodeTables::script(ucs4); + QFontEngine *engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return false; + QString utf16 = QString::fromUcs4(&ucs4, 1); + return engine->canRender(utf16.data(), utf16.length()); +} + +/*! + Returns the left bearing of character \a ch in the font. + + The left bearing is the right-ward distance of the left-most pixel + of the character from the logical origin of the character. This + value is negative if the pixels of the character extend to the + left of the logical origin. + + See width(QChar) for a graphical description of this metric. + + \sa rightBearing(), minLeftBearing(), width() +*/ +int QFontMetrics::leftBearing(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return 0; + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<10> glyphs; + int nglyphs = 9; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + // ### can nglyphs != 1 happen at all? Not currently I think + qreal lb; + engine->getGlyphBearings(glyphs.glyphs[0], &lb); + return qRound(lb); +} + +/*! + Returns the right bearing of character \a ch in the font. + + The right bearing is the left-ward distance of the right-most + pixel of the character from the logical origin of a subsequent + character. This value is negative if the pixels of the character + extend to the right of the width() of the character. + + See width() for a graphical description of this metric. + + \sa leftBearing(), minRightBearing(), width() +*/ +int QFontMetrics::rightBearing(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return 0; + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<10> glyphs; + int nglyphs = 9; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + // ### can nglyphs != 1 happen at all? Not currently I think + qreal rb; + engine->getGlyphBearings(glyphs.glyphs[0], 0, &rb); + return qRound(rb); +} + +/*! + Returns the width in pixels of the first \a len characters of \a + text. If \a len is negative (the default), the entire string is + used. + + Note that this value is \e not equal to boundingRect().width(); + boundingRect() returns a rectangle describing the pixels this + string will cover whereas width() returns the distance to where + the next string should be drawn. + + \sa boundingRect() +*/ +int QFontMetrics::width(const QString &text, int len) const +{ + return width(text, len, 0); +} + +/*! + \internal +*/ +int QFontMetrics::width(const QString &text, int len, int flags) const +{ + int pos = text.indexOf(QLatin1Char('\x9c')); + if (pos != -1) { + len = (len < 0) ? pos : qMin(pos, len); + } else if (len < 0) { + len = text.length(); + } + if (len == 0) + return 0; + + if (flags & Qt::TextBypassShaping) { + // Skip harfbuzz complex shaping, only use advances + int numGlyphs = len; + QVarLengthGlyphLayoutArray glyphs(numGlyphs); + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + if (!engine->stringToCMap(text.data(), len, &glyphs, &numGlyphs, 0)) { + glyphs.resize(numGlyphs); + if (!engine->stringToCMap(text.data(), len, &glyphs, &numGlyphs, 0)) + Q_ASSERT_X(false, Q_FUNC_INFO, "stringToCMap shouldn't fail twice"); + } + + QFixed width; + for (int i = 0; i < numGlyphs; ++i) + width += glyphs.advances_x[i]; + return qRound(width); + } + + QStackTextEngine layout(text, d.data()); + layout.ignoreBidi = true; + return qRound(layout.width(0, len)); +} + +/*! + \overload + + \img bearings.png Bearings + + Returns the logical width of character \a ch in pixels. This is a + distance appropriate for drawing a subsequent character after \a + ch. + + Some of the metrics are described in the image to the right. The + central dark rectangles cover the logical width() of each + character. The outer pale rectangles cover the leftBearing() and + rightBearing() of each character. Notice that the bearings of "f" + in this particular font are both negative, while the bearings of + "o" are both positive. + + \warning This function will produce incorrect results for Arabic + characters or non-spacing marks in the middle of a string, as the + glyph shaping and positioning of marks that happens when + processing strings cannot be taken into account. When implementing + an interactive text control, use QTextLayout instead. + + \sa boundingRect() +*/ +int QFontMetrics::width(QChar ch) const +{ + if (QChar::category(ch.unicode()) == QChar::Mark_NonSpacing) + return 0; + + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + return qRound(glyphs.advances_x[0]); +} + +/*! \obsolete + + Returns the width of the character at position \a pos in the + string \a text. + + The whole string is needed, as the glyph drawn may change + depending on the context (the letter before and after the current + one) for some languages (e.g. Arabic). + + This function also takes non spacing marks and ligatures into + account. +*/ +int QFontMetrics::charWidth(const QString &text, int pos) const +{ + if (pos < 0 || pos > (int)text.length()) + return 0; + + QChar ch = text.unicode()[pos]; + const int script = QUnicodeTables::script(ch); + int width; + + if (script != QUnicodeTables::Common) { + // complex script shaping. Have to do some hard work + int from = qMax(0, pos - 8); + int to = qMin(text.length(), pos + 8); + QString cstr = QString::fromRawData(text.unicode() + from, to - from); + QStackTextEngine layout(cstr, d.data()); + layout.ignoreBidi = true; + layout.itemize(); + width = qRound(layout.width(pos-from, 1)); + } else if (QChar::category(ch.unicode()) == QChar::Mark_NonSpacing) { + width = 0; + } else { + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + width = qRound(glyphs.advances_x[0]); + } + return width; +} + +/*! + Returns the bounding rectangle of the characters in the string + specified by \a text. The bounding rectangle always covers at least + the set of pixels the text would cover if drawn at (0, 0). + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts, and that the width of the returned + rectangle might be different than what the width() method returns. + + If you want to know the advance width of the string (to layout + a set of strings next to each other), use width() instead. + + Newline characters are processed as normal characters, \e not as + linebreaks. + + The height of the bounding rectangle is at least as large as the + value returned by height(). + + \sa width(), height(), QPainter::boundingRect(), tightBoundingRect() +*/ +QRect QFontMetrics::boundingRect(const QString &text) const +{ + if (text.length() == 0) + return QRect(); + + QStackTextEngine layout(text, d.data()); + layout.ignoreBidi = true; + layout.itemize(); + glyph_metrics_t gm = layout.boundingBox(0, text.length()); + return QRect(qRound(gm.x), qRound(gm.y), qRound(gm.width), qRound(gm.height)); +} + +/*! + Returns the rectangle that is covered by ink if character \a ch + were to be drawn at the origin of the coordinate system. + + Note that the bounding rectangle may extend to the left of (0, 0) + (e.g., for italicized fonts), and that the text output may cover \e + all pixels in the bounding rectangle. For a space character the rectangle + will usually be empty. + + Note that the rectangle usually extends both above and below the + base line. + + \warning The width of the returned rectangle is not the advance width + of the character. Use boundingRect(const QString &) or width() instead. + + \sa width() +*/ +QRect QFontMetrics::boundingRect(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<10> glyphs; + int nglyphs = 9; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + glyph_metrics_t gm = engine->boundingBox(glyphs.glyphs[0]); + return QRect(qRound(gm.x), qRound(gm.y), qRound(gm.width), qRound(gm.height)); +} + +/*! + \overload + + Returns the bounding rectangle of the characters in the string + specified by \a text, which is the set of pixels the text would + cover if drawn at (0, 0). The drawing, and hence the bounding + rectangle, is constrained to the rectangle \a rect. + + The \a flags argument is the bitwise OR of the following flags: + \list + \o Qt::AlignLeft aligns to the left border, except for + Arabic and Hebrew where it aligns to the right. + \o Qt::AlignRight aligns to the right border, except for + Arabic and Hebrew where it aligns to the left. + \o Qt::AlignJustify produces justified text. + \o Qt::AlignHCenter aligns horizontally centered. + \o Qt::AlignTop aligns to the top border. + \o Qt::AlignBottom aligns to the bottom border. + \o Qt::AlignVCenter aligns vertically centered + \o Qt::AlignCenter (== \c{Qt::AlignHCenter | Qt::AlignVCenter}) + \o Qt::TextSingleLine ignores newline characters in the text. + \o Qt::TextExpandTabs expands tabs (see below) + \o Qt::TextShowMnemonic interprets "&x" as \underline{x}; i.e., underlined. + \o Qt::TextWordWrap breaks the text to fit the rectangle. + \endlist + + Qt::Horizontal alignment defaults to Qt::AlignLeft and vertical + alignment defaults to Qt::AlignTop. + + If several of the horizontal or several of the vertical alignment + flags are set, the resulting alignment is undefined. + + If Qt::TextExpandTabs is set in \a flags, then: if \a tabArray is + non-null, it specifies a 0-terminated sequence of pixel-positions + for tabs; otherwise if \a tabStops is non-zero, it is used as the + tab spacing (in pixels). + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts, and that the text output may cover \e + all pixels in the bounding rectangle. + + Newline characters are processed as linebreaks. + + Despite the different actual character heights, the heights of the + bounding rectangles of "Yes" and "yes" are the same. + + The bounding rectangle returned by this function is somewhat larger + than that calculated by the simpler boundingRect() function. This + function uses the \link minLeftBearing() maximum left \endlink and + \link minRightBearing() right \endlink font bearings as is + necessary for multi-line text to align correctly. Also, + fontHeight() and lineSpacing() are used to calculate the height, + rather than individual character heights. + + \sa width(), QPainter::boundingRect(), Qt::Alignment +*/ +QRect QFontMetrics::boundingRect(const QRect &rect, int flags, const QString &text, int tabStops, + int *tabArray) const +{ + int tabArrayLen = 0; + if (tabArray) + while (tabArray[tabArrayLen]) + tabArrayLen++; + + QRectF rb; + QRectF rr(rect); + qt_format_text(QFont(d.data()), rr, flags | Qt::TextDontPrint, text, &rb, tabStops, tabArray, + tabArrayLen, 0); + + return rb.toAlignedRect(); +} + +/*! + Returns the size in pixels of \a text. + + The \a flags argument is the bitwise OR of the following flags: + \list + \o Qt::TextSingleLine ignores newline characters. + \o Qt::TextExpandTabs expands tabs (see below) + \o Qt::TextShowMnemonic interprets "&x" as \underline{x}; i.e., underlined. + \o Qt::TextWordBreak breaks the text to fit the rectangle. + \endlist + + If Qt::TextExpandTabs is set in \a flags, then: if \a tabArray is + non-null, it specifies a 0-terminated sequence of pixel-positions + for tabs; otherwise if \a tabStops is non-zero, it is used as the + tab spacing (in pixels). + + Newline characters are processed as linebreaks. + + Despite the different actual character heights, the heights of the + bounding rectangles of "Yes" and "yes" are the same. + + \sa boundingRect() +*/ +QSize QFontMetrics::size(int flags, const QString &text, int tabStops, int *tabArray) const +{ + return boundingRect(QRect(0,0,0,0), flags | Qt::TextLongestVariant, text, tabStops, tabArray).size(); +} + +/*! + \since 4.3 + + Returns a tight bounding rectangle around the characters in the + string specified by \a text. The bounding rectangle always covers + at least the set of pixels the text would cover if drawn at (0, + 0). + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts, and that the width of the returned + rectangle might be different than what the width() method returns. + + If you want to know the advance width of the string (to layout + a set of strings next to each other), use width() instead. + + Newline characters are processed as normal characters, \e not as + linebreaks. + + \warning Calling this method is very slow on Windows. + + \sa width(), height(), boundingRect() +*/ +QRect QFontMetrics::tightBoundingRect(const QString &text) const +{ + if (text.length() == 0) + return QRect(); + + QStackTextEngine layout(text, d.data()); + layout.ignoreBidi = true; + layout.itemize(); + glyph_metrics_t gm = layout.tightBoundingBox(0, text.length()); + return QRect(qRound(gm.x), qRound(gm.y), qRound(gm.width), qRound(gm.height)); +} + + +/*! + \since 4.2 + + If the string \a text is wider than \a width, returns an elided + version of the string (i.e., a string with "..." in it). + Otherwise, returns the original string. + + The \a mode parameter specifies whether the text is elided on the + left (e.g., "...tech"), in the middle (e.g., "Tr...ch"), or on + the right (e.g., "Trol..."). + + The \a width is specified in pixels, not characters. + + The \a flags argument is optional and currently only supports + Qt::TextShowMnemonic as value. + + The elide mark will follow the \l{Qt::LayoutDirection}{layout + direction}; it will be on the right side of the text for + right-to-left layouts, and on the left side for right-to-left + layouts. Note that this behavior is independent of the text + language. +*/ +QString QFontMetrics::elidedText(const QString &text, Qt::TextElideMode mode, int width, int flags) const +{ + QString _text = text; + if (!(flags & Qt::TextLongestVariant)) { + int posA = 0; + int posB = _text.indexOf(QLatin1Char('\x9c')); + while (posB >= 0) { + QString portion = _text.mid(posA, posB - posA); + if (size(flags, portion).width() <= width) + return portion; + posA = posB + 1; + posB = _text.indexOf(QLatin1Char('\x9c'), posA); + } + _text = _text.mid(posA); + } + QStackTextEngine engine(_text, QFont(d.data())); + return engine.elidedText(mode, width, flags); +} + +/*! + Returns the distance from the base line to where an underscore + should be drawn. + + \sa overlinePos(), strikeOutPos(), lineWidth() +*/ +int QFontMetrics::underlinePos() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->underlinePosition()); +} + +/*! + Returns the distance from the base line to where an overline + should be drawn. + + \sa underlinePos(), strikeOutPos(), lineWidth() +*/ +int QFontMetrics::overlinePos() const +{ + return ascent() + 1; +} + +/*! + Returns the distance from the base line to where the strikeout + line should be drawn. + + \sa underlinePos(), overlinePos(), lineWidth() +*/ +int QFontMetrics::strikeOutPos() const +{ + int pos = ascent() / 3; + return pos > 0 ? pos : 1; +} + +/*! + Returns the width of the underline and strikeout lines, adjusted + for the point size of the font. + + \sa underlinePos(), overlinePos(), strikeOutPos() +*/ +int QFontMetrics::lineWidth() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return qRound(engine->lineThickness()); +} + + + + +/***************************************************************************** + QFontMetricsF member functions + *****************************************************************************/ + +/*! + \class QFontMetricsF + \reentrant + + \brief The QFontMetricsF class provides font metrics information. + + \ingroup painting + \ingroup shared + + QFontMetricsF functions calculate the size of characters and + strings for a given font. You can construct a QFontMetricsF object + with an existing QFont to obtain metrics for that font. If the + font is changed later, the font metrics object is \e not updated. + + Once created, the object provides functions to access the + individual metrics of the font, its characters, and for strings + rendered in the font. + + There are several functions that operate on the font: ascent(), + descent(), height(), leading() and lineSpacing() return the basic + size properties of the font. The underlinePos(), overlinePos(), + strikeOutPos() and lineWidth() functions, return the properties of + the line that underlines, overlines or strikes out the + characters. These functions are all fast. + + There are also some functions that operate on the set of glyphs in + the font: minLeftBearing(), minRightBearing() and maxWidth(). + These are by necessity slow, and we recommend avoiding them if + possible. + + For each character, you can get its width(), leftBearing() and + rightBearing() and find out whether it is in the font using + inFont(). You can also treat the character as a string, and use + the string functions on it. + + The string functions include width(), to return the width of a + string in pixels (or points, for a printer), boundingRect(), to + return a rectangle large enough to contain the rendered string, + and size(), to return the size of that rectangle. + + Example: + \snippet doc/src/snippets/code/src_gui_text_qfontmetrics.cpp 1 + + \sa QFont QFontInfo QFontDatabase +*/ + +/*! + \since 4.2 + + Constructs a font metrics object with floating point precision + from the given \a fontMetrics object. +*/ +QFontMetricsF::QFontMetricsF(const QFontMetrics &fontMetrics) + : d(fontMetrics.d.data()) +{ +} + +/*! + \since 4.2 + + Assigns \a other to this object. +*/ +QFontMetricsF &QFontMetricsF::operator=(const QFontMetrics &other) +{ + d = other.d.data(); + return *this; +} + +/*! + Constructs a font metrics object for \a font. + + The font metrics will be compatible with the paintdevice used to + create \a font. + + The font metrics object holds the information for the font that is + passed in the constructor at the time it is created, and is not + updated if the font's attributes are changed later. + + Use QFontMetricsF(const QFont &, QPaintDevice *) to get the font + metrics that are compatible with a certain paint device. +*/ +QFontMetricsF::QFontMetricsF(const QFont &font) + : d(font.d.data()) +{ +} + +/*! + Constructs a font metrics object for \a font and \a paintdevice. + + The font metrics will be compatible with the paintdevice passed. + If the \a paintdevice is 0, the metrics will be screen-compatible, + ie. the metrics you get if you use the font for drawing text on a + \link QWidget widgets\endlink or \link QPixmap pixmaps\endlink, + not on a QPicture or QPrinter. + + The font metrics object holds the information for the font that is + passed in the constructor at the time it is created, and is not + updated if the font's attributes are changed later. +*/ +QFontMetricsF::QFontMetricsF(const QFont &font, QPaintDevice *paintdevice) +{ + int dpi = paintdevice ? paintdevice->logicalDpiY() : qt_defaultDpi(); +#ifdef Q_WS_X11 + const QX11Info *info = qt_x11Info(paintdevice); + int screen = info ? info->screen() : 0; +#else + const int screen = 0; +#endif + if (font.d->dpi != dpi || font.d->screen != screen ) { + d = new QFontPrivate(*font.d); + d->dpi = dpi; + d->screen = screen; + } else { + d = font.d.data(); + } + +} + +/*! + Constructs a copy of \a fm. +*/ +QFontMetricsF::QFontMetricsF(const QFontMetricsF &fm) + : d(fm.d.data()) +{ +} + +/*! + Destroys the font metrics object and frees all allocated + resources. +*/ +QFontMetricsF::~QFontMetricsF() +{ +} + +/*! + Assigns the font metrics \a fm to this font metrics object. +*/ +QFontMetricsF &QFontMetricsF::operator=(const QFontMetricsF &fm) +{ + d = fm.d.data(); + return *this; +} + +/*! + \overload + Returns true if the font metrics are equal to the \a other font + metrics; otherwise returns false. + + Two font metrics are considered equal if they were constructed from the + same QFont and the paint devices they were constructed for are + considered to be compatible. +*/ +bool QFontMetricsF::operator ==(const QFontMetricsF &other) const +{ + return d == other.d; +} + +/*! + Returns true if the font metrics are equal to the \a other font + metrics; otherwise returns false. + + Two font metrics are considered equal if they were constructed from the + same QFont and the paint devices they were constructed for are + considered to be compatible. +*/ +bool QFontMetricsF::operator ==(const QFontMetricsF &other) +{ + return d == other.d; +} + +/*! + \fn bool QFontMetricsF::operator!=(const QFontMetricsF &other) + + Returns true if the font metrics are not equal to the \a other font + metrics; otherwise returns false. + + \sa operator==() +*/ + +/*! + \fn bool QFontMetricsF::operator !=(const QFontMetricsF &other) const + \overload + + Returns true if the font metrics are not equal to the \a other font + metrics; otherwise returns false. + + \sa operator==() +*/ + +/*! + Returns the ascent of the font. + + The ascent of a font is the distance from the baseline to the + highest position characters extend to. In practice, some font + designers break this rule, e.g. when they put more than one accent + on top of a character, or to accommodate an unusual character in + an exotic language, so it is possible (though rare) that this + value will be too small. + + \sa descent() +*/ +qreal QFontMetricsF::ascent() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->ascent().toReal(); +} + + +/*! + Returns the descent of the font. + + The descent is the distance from the base line to the lowest point + characters extend to. (Note that this is different from X, which + adds 1 pixel.) In practice, some font designers break this rule, + e.g. to accommodate an unusual character in an exotic language, so + it is possible (though rare) that this value will be too small. + + \sa ascent() +*/ +qreal QFontMetricsF::descent() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->descent().toReal(); +} + +/*! + Returns the height of the font. + + This is always equal to ascent()+descent()+1 (the 1 is for the + base line). + + \sa leading(), lineSpacing() +*/ +qreal QFontMetricsF::height() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + + return (engine->ascent() + engine->descent() + 1).toReal(); +} + +/*! + Returns the leading of the font. + + This is the natural inter-line spacing. + + \sa height(), lineSpacing() +*/ +qreal QFontMetricsF::leading() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->leading().toReal(); +} + +/*! + Returns the distance from one base line to the next. + + This value is always equal to leading()+height(). + + \sa height(), leading() +*/ +qreal QFontMetricsF::lineSpacing() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return (engine->leading() + engine->ascent() + engine->descent() + 1).toReal(); +} + +/*! + Returns the minimum left bearing of the font. + + This is the smallest leftBearing(char) of all characters in the + font. + + Note that this function can be very slow if the font is large. + + \sa minRightBearing(), leftBearing() +*/ +qreal QFontMetricsF::minLeftBearing() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->minLeftBearing(); +} + +/*! + Returns the minimum right bearing of the font. + + This is the smallest rightBearing(char) of all characters in the + font. + + Note that this function can be very slow if the font is large. + + \sa minLeftBearing(), rightBearing() +*/ +qreal QFontMetricsF::minRightBearing() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->minRightBearing(); +} + +/*! + Returns the width of the widest character in the font. +*/ +qreal QFontMetricsF::maxWidth() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->maxCharWidth(); +} + +/*! + Returns the 'x' height of the font. This is often but not always + the same as the height of the character 'x'. +*/ +qreal QFontMetricsF::xHeight() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + if (d->capital == QFont::SmallCaps) + return d->smallCapsFontPrivate()->engineForScript(QUnicodeTables::Common)->ascent().toReal(); + return engine->xHeight().toReal(); +} + +/*! + \since 4.2 + + Returns the average width of glyphs in the font. +*/ +qreal QFontMetricsF::averageCharWidth() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->averageCharWidth().toReal(); +} + +/*! + Returns true if character \a ch is a valid character in the font; + otherwise returns false. +*/ +bool QFontMetricsF::inFont(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return false; + return engine->canRender(&ch, 1); +} + +/*! + Returns true if the character encoded in UCS-4/UTF-32 is a valid + character in the font; otherwise returns false. +*/ +bool QFontMetricsF::inFontUcs4(uint ucs4) const +{ + const int script = QUnicodeTables::script(ucs4); + QFontEngine *engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return false; + QString utf16 = QString::fromUcs4(&ucs4, 1); + return engine->canRender(utf16.data(), utf16.length()); +} + +/*! + Returns the left bearing of character \a ch in the font. + + The left bearing is the right-ward distance of the left-most pixel + of the character from the logical origin of the character. This + value is negative if the pixels of the character extend to the + left of the logical origin. + + See width(QChar) for a graphical description of this metric. + + \sa rightBearing(), minLeftBearing(), width() +*/ +qreal QFontMetricsF::leftBearing(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return 0; + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<10> glyphs; + int nglyphs = 9; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + // ### can nglyphs != 1 happen at all? Not currently I think + qreal lb; + engine->getGlyphBearings(glyphs.glyphs[0], &lb); + return lb; +} + +/*! + Returns the right bearing of character \a ch in the font. + + The right bearing is the left-ward distance of the right-most + pixel of the character from the logical origin of a subsequent + character. This value is negative if the pixels of the character + extend to the right of the width() of the character. + + See width() for a graphical description of this metric. + + \sa leftBearing(), minRightBearing(), width() +*/ +qreal QFontMetricsF::rightBearing(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + if (engine->type() == QFontEngine::Box) + return 0; + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<10> glyphs; + int nglyphs = 9; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + // ### can nglyphs != 1 happen at all? Not currently I think + qreal rb; + engine->getGlyphBearings(glyphs.glyphs[0], 0, &rb); + return rb; + +} + +/*! + Returns the width in pixels of the characters in the given \a text. + + Note that this value is \e not equal to the width returned by + boundingRect().width() because boundingRect() returns a rectangle + describing the pixels this string will cover whereas width() + returns the distance to where the next string should be drawn. + + \sa boundingRect() +*/ +qreal QFontMetricsF::width(const QString &text) const +{ + int pos = text.indexOf(QLatin1Char('\x9c')); + int len = (pos != -1) ? pos : text.length(); + + QStackTextEngine layout(text, d.data()); + layout.ignoreBidi = true; + layout.itemize(); + return layout.width(0, len).toReal(); +} + +/*! + \overload + + \img bearings.png Bearings + + Returns the logical width of character \a ch in pixels. This is a + distance appropriate for drawing a subsequent character after \a + ch. + + Some of the metrics are described in the image to the right. The + central dark rectangles cover the logical width() of each + character. The outer pale rectangles cover the leftBearing() and + rightBearing() of each character. Notice that the bearings of "f" + in this particular font are both negative, while the bearings of + "o" are both positive. + + \warning This function will produce incorrect results for Arabic + characters or non-spacing marks in the middle of a string, as the + glyph shaping and positioning of marks that happens when + processing strings cannot be taken into account. When implementing + an interactive text control, use QTextLayout instead. + + \sa boundingRect() +*/ +qreal QFontMetricsF::width(QChar ch) const +{ + if (QChar::category(ch.unicode()) == QChar::Mark_NonSpacing) + return 0.; + + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + return glyphs.advances_x[0].toReal(); +} + +/*! + Returns the bounding rectangle of the characters in the string + specified by \a text. The bounding rectangle always covers at least + the set of pixels the text would cover if drawn at (0, 0). + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts, and that the width of the returned + rectangle might be different than what the width() method returns. + + If you want to know the advance width of the string (to layout + a set of strings next to each other), use width() instead. + + Newline characters are processed as normal characters, \e not as + linebreaks. + + The height of the bounding rectangle is at least as large as the + value returned height(). + + \sa width(), height(), QPainter::boundingRect() +*/ +QRectF QFontMetricsF::boundingRect(const QString &text) const +{ + int len = text.length(); + if (len == 0) + return QRectF(); + + QStackTextEngine layout(text, d.data()); + layout.ignoreBidi = true; + layout.itemize(); + glyph_metrics_t gm = layout.boundingBox(0, len); + return QRectF(gm.x.toReal(), gm.y.toReal(), + gm.width.toReal(), gm.height.toReal()); +} + +/*! + Returns the bounding rectangle of the character \a ch relative to + the left-most point on the base line. + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts, and that the text output may cover \e + all pixels in the bounding rectangle. + + Note that the rectangle usually extends both above and below the + base line. + + \sa width() +*/ +QRectF QFontMetricsF::boundingRect(QChar ch) const +{ + const int script = QUnicodeTables::script(ch); + QFontEngine *engine; + if (d->capital == QFont::SmallCaps && ch.isLower()) + engine = d->smallCapsFontPrivate()->engineForScript(script); + else + engine = d->engineForScript(script); + Q_ASSERT(engine != 0); + + d->alterCharForCapitalization(ch); + + QGlyphLayoutArray<10> glyphs; + int nglyphs = 9; + engine->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + glyph_metrics_t gm = engine->boundingBox(glyphs.glyphs[0]); + return QRectF(gm.x.toReal(), gm.y.toReal(), gm.width.toReal(), gm.height.toReal()); +} + +/*! + \overload + + Returns the bounding rectangle of the characters in the given \a text. + This is the set of pixels the text would cover if drawn when constrained + to the bounding rectangle specified by \a rect. + + The \a flags argument is the bitwise OR of the following flags: + \list + \o Qt::AlignLeft aligns to the left border, except for + Arabic and Hebrew where it aligns to the right. + \o Qt::AlignRight aligns to the right border, except for + Arabic and Hebrew where it aligns to the left. + \o Qt::AlignJustify produces justified text. + \o Qt::AlignHCenter aligns horizontally centered. + \o Qt::AlignTop aligns to the top border. + \o Qt::AlignBottom aligns to the bottom border. + \o Qt::AlignVCenter aligns vertically centered + \o Qt::AlignCenter (== \c{Qt::AlignHCenter | Qt::AlignVCenter}) + \o Qt::TextSingleLine ignores newline characters in the text. + \o Qt::TextExpandTabs expands tabs (see below) + \o Qt::TextShowMnemonic interprets "&x" as \underline{x}; i.e., underlined. + \o Qt::TextWordWrap breaks the text to fit the rectangle. + \endlist + + Qt::Horizontal alignment defaults to Qt::AlignLeft and vertical + alignment defaults to Qt::AlignTop. + + If several of the horizontal or several of the vertical alignment + flags are set, the resulting alignment is undefined. + + These flags are defined in \l{Qt::AlignmentFlag}. + + If Qt::TextExpandTabs is set in \a flags, the following behavior is + used to interpret tab characters in the text: + \list + \o If \a tabArray is non-null, it specifies a 0-terminated sequence of + pixel-positions for tabs in the text. + \o If \a tabStops is non-zero, it is used as the tab spacing (in pixels). + \endlist + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts. + + Newline characters are processed as line breaks. + + Despite the different actual character heights, the heights of the + bounding rectangles of "Yes" and "yes" are the same. + + The bounding rectangle returned by this function is somewhat larger + than that calculated by the simpler boundingRect() function. This + function uses the \link minLeftBearing() maximum left \endlink and + \link minRightBearing() right \endlink font bearings as is + necessary for multi-line text to align correctly. Also, + fontHeight() and lineSpacing() are used to calculate the height, + rather than individual character heights. + + \sa width(), QPainter::boundingRect(), Qt::Alignment +*/ +QRectF QFontMetricsF::boundingRect(const QRectF &rect, int flags, const QString& text, + int tabStops, int *tabArray) const +{ + int tabArrayLen = 0; + if (tabArray) + while (tabArray[tabArrayLen]) + tabArrayLen++; + + QRectF rb; + qt_format_text(QFont(d.data()), rect, flags | Qt::TextDontPrint, text, &rb, tabStops, tabArray, + tabArrayLen, 0); + return rb; +} + +/*! + Returns the size in pixels of the characters in the given \a text. + + The \a flags argument is the bitwise OR of the following flags: + \list + \o Qt::TextSingleLine ignores newline characters. + \o Qt::TextExpandTabs expands tabs (see below) + \o Qt::TextShowMnemonic interprets "&x" as \underline{x}; i.e., underlined. + \o Qt::TextWordBreak breaks the text to fit the rectangle. + \endlist + + These flags are defined in \l{Qt::TextFlags}. + + If Qt::TextExpandTabs is set in \a flags, the following behavior is + used to interpret tab characters in the text: + \list + \o If \a tabArray is non-null, it specifies a 0-terminated sequence of + pixel-positions for tabs in the text. + \o If \a tabStops is non-zero, it is used as the tab spacing (in pixels). + \endlist + + Newline characters are processed as line breaks. + + Note: Despite the different actual character heights, the heights of the + bounding rectangles of "Yes" and "yes" are the same. + + \sa boundingRect() +*/ +QSizeF QFontMetricsF::size(int flags, const QString &text, int tabStops, int *tabArray) const +{ + return boundingRect(QRectF(), flags | Qt::TextLongestVariant, text, tabStops, tabArray).size(); +} + +/*! + \since 4.3 + + Returns a tight bounding rectangle around the characters in the + string specified by \a text. The bounding rectangle always covers + at least the set of pixels the text would cover if drawn at (0, + 0). + + Note that the bounding rectangle may extend to the left of (0, 0), + e.g. for italicized fonts, and that the width of the returned + rectangle might be different than what the width() method returns. + + If you want to know the advance width of the string (to layout + a set of strings next to each other), use width() instead. + + Newline characters are processed as normal characters, \e not as + linebreaks. + + \warning Calling this method is very slow on Windows. + + \sa width(), height(), boundingRect() +*/ +QRectF QFontMetricsF::tightBoundingRect(const QString &text) const +{ + if (text.length() == 0) + return QRect(); + + QStackTextEngine layout(text, d.data()); + layout.ignoreBidi = true; + layout.itemize(); + glyph_metrics_t gm = layout.tightBoundingBox(0, text.length()); + return QRectF(gm.x.toReal(), gm.y.toReal(), gm.width.toReal(), gm.height.toReal()); +} + +/*! + \since 4.2 + + If the string \a text is wider than \a width, returns an elided + version of the string (i.e., a string with "..." in it). + Otherwise, returns the original string. + + The \a mode parameter specifies whether the text is elided on the + left (e.g., "...tech"), in the middle (e.g., "Tr...ch"), or on + the right (e.g., "Trol..."). + + The \a width is specified in pixels, not characters. + + The \a flags argument is optional and currently only supports + Qt::TextShowMnemonic as value. +*/ +QString QFontMetricsF::elidedText(const QString &text, Qt::TextElideMode mode, qreal width, int flags) const +{ + QString _text = text; + if (!(flags & Qt::TextLongestVariant)) { + int posA = 0; + int posB = _text.indexOf(QLatin1Char('\x9c')); + while (posB >= 0) { + QString portion = _text.mid(posA, posB - posA); + if (size(flags, portion).width() <= width) + return portion; + posA = posB + 1; + posB = _text.indexOf(QLatin1Char('\x9c'), posA); + } + _text = _text.mid(posA); + } + QStackTextEngine engine(_text, QFont(d.data())); + return engine.elidedText(mode, QFixed::fromReal(width), flags); +} + +/*! + Returns the distance from the base line to where an underscore + should be drawn. + + \sa overlinePos(), strikeOutPos(), lineWidth() +*/ +qreal QFontMetricsF::underlinePos() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->underlinePosition().toReal(); +} + +/*! + Returns the distance from the base line to where an overline + should be drawn. + + \sa underlinePos(), strikeOutPos(), lineWidth() +*/ +qreal QFontMetricsF::overlinePos() const +{ + return ascent() + 1; +} + +/*! + Returns the distance from the base line to where the strikeout + line should be drawn. + + \sa underlinePos(), overlinePos(), lineWidth() +*/ +qreal QFontMetricsF::strikeOutPos() const +{ + return ascent() / 3.; +} + +/*! + Returns the width of the underline and strikeout lines, adjusted + for the point size of the font. + + \sa underlinePos(), overlinePos(), strikeOutPos() +*/ +qreal QFontMetricsF::lineWidth() const +{ + QFontEngine *engine = d->engineForScript(QUnicodeTables::Common); + Q_ASSERT(engine != 0); + return engine->lineThickness().toReal(); +} + +/*! + \fn QSize QFontMetrics::size(int flags, const QString &text, int len, + int tabStops, int *tabArray) const + \compat + + Use the size() function in combination with QString::left() + instead. + + \oldcode + QSize size = size(flags, str, len, tabstops, tabarray); + \newcode + QSize size = size(flags, str.left(len), tabstops, tabarray); + \endcode +*/ + +/*! + \fn QRect QFontMetrics::boundingRect(int x, int y, int w, int h, int flags, + const QString& text, int len, int tabStops, int *tabArray) const + \compat + + Use the boundingRect() function in combination with + QString::left() and a QRect constructor instead. + + \oldcode + QRect rect = boundingRect(x, y, w, h , flags, text, len, + tabStops, tabArray); + \newcode + QRect rect = boundingRect(QRect(x, y, w, h), flags, text.left(len), + tabstops, tabarray); + \endcode + +*/ + +/*! + \fn QRect QFontMetrics::boundingRect(const QString &text, int len) const + \compat + + Use the boundingRect() function in combination with + QString::left() instead. + + \oldcode + QRect rect = boundingRect(text, len); + \newcode + QRect rect = boundingRect(text.left(len)); + \endcode +*/ + +QT_END_NAMESPACE diff --git a/src/gui/text/qfontmetrics.h b/src/gui/text/qfontmetrics.h new file mode 100644 index 0000000000..5fe8676993 --- /dev/null +++ b/src/gui/text/qfontmetrics.h @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTMETRICS_H +#define QFONTMETRICS_H + +#include +#include +#ifndef QT_INCLUDE_COMPAT +#include +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#ifdef Q_WS_QWS +class QFontEngine; +#endif + +class QTextCodec; +class QRect; + + +class Q_GUI_EXPORT QFontMetrics +{ +public: + QFontMetrics(const QFont &); + QFontMetrics(const QFont &, QPaintDevice *pd); + QFontMetrics(const QFontMetrics &); + ~QFontMetrics(); + + QFontMetrics &operator=(const QFontMetrics &); +#ifdef Q_COMPILER_RVALUE_REFS + inline QFontMetrics &operator=(QFontMetrics &&other) + { qSwap(d, other.d); return *this; } +#endif + + int ascent() const; + int descent() const; + int height() const; + int leading() const; + int lineSpacing() const; + int minLeftBearing() const; + int minRightBearing() const; + int maxWidth() const; + + int xHeight() const; + int averageCharWidth() const; + + bool inFont(QChar) const; + bool inFontUcs4(uint ucs4) const; + + int leftBearing(QChar) const; + int rightBearing(QChar) const; + int width(const QString &, int len = -1) const; + int width(const QString &, int len, int flags) const; + + int width(QChar) const; + int charWidth(const QString &str, int pos) const; + + QRect boundingRect(QChar) const; + + QRect boundingRect(const QString &text) const; + QRect boundingRect(const QRect &r, int flags, const QString &text, int tabstops=0, int *tabarray=0) const; + inline QRect boundingRect(int x, int y, int w, int h, int flags, const QString &text, + int tabstops=0, int *tabarray=0) const + { return boundingRect(QRect(x, y, w, h), flags, text, tabstops, tabarray); } + QSize size(int flags, const QString& str, int tabstops=0, int *tabarray=0) const; + + QRect tightBoundingRect(const QString &text) const; + + QString elidedText(const QString &text, Qt::TextElideMode mode, int width, int flags = 0) const; + + int underlinePos() const; + int overlinePos() const; + int strikeOutPos() const; + int lineWidth() const; + + bool operator==(const QFontMetrics &other); // 5.0 - remove me + bool operator==(const QFontMetrics &other) const; + inline bool operator !=(const QFontMetrics &other) { return !operator==(other); } // 5.0 - remove me + inline bool operator !=(const QFontMetrics &other) const { return !operator==(other); } + +#ifdef QT3_SUPPORT + inline QRect boundingRect(const QString &text, int len) const + { return boundingRect(text.left(len)); } + inline QRect boundingRect(int x, int y, int w, int h, int flags, const QString& str, int len, + int tabstops=0, int *tabarray=0) const + { return boundingRect(QRect(x, y, w, h), flags, str.left(len), tabstops, tabarray); } + inline QSize size(int flags, const QString& str, int len, int tabstops=0, int *tabarray=0) const + { return size(flags, str.left(len), tabstops, tabarray); } +#endif +private: +#if defined(Q_WS_MAC) + friend class QFontPrivate; +#endif + friend class QFontMetricsF; + friend class QStackTextEngine; + + QExplicitlySharedDataPointer d; +}; + + +class Q_GUI_EXPORT QFontMetricsF +{ +public: + QFontMetricsF(const QFont &); + QFontMetricsF(const QFont &, QPaintDevice *pd); + QFontMetricsF(const QFontMetrics &); + QFontMetricsF(const QFontMetricsF &); + ~QFontMetricsF(); + + QFontMetricsF &operator=(const QFontMetricsF &); + QFontMetricsF &operator=(const QFontMetrics &); +#ifdef Q_COMPILER_RVALUE_REFS + inline QFontMetricsF &operator=(QFontMetricsF &&other) + { qSwap(d, other.d); return *this; } +#endif + qreal ascent() const; + qreal descent() const; + qreal height() const; + qreal leading() const; + qreal lineSpacing() const; + qreal minLeftBearing() const; + qreal minRightBearing() const; + qreal maxWidth() const; + + qreal xHeight() const; + qreal averageCharWidth() const; + + bool inFont(QChar) const; + bool inFontUcs4(uint ucs4) const; + + qreal leftBearing(QChar) const; + qreal rightBearing(QChar) const; + qreal width(const QString &string) const; + + qreal width(QChar) const; + + QRectF boundingRect(const QString &string) const; + QRectF boundingRect(QChar) const; + QRectF boundingRect(const QRectF &r, int flags, const QString& string, int tabstops=0, int *tabarray=0) const; + QSizeF size(int flags, const QString& str, int tabstops=0, int *tabarray=0) const; + + QRectF tightBoundingRect(const QString &text) const; + + QString elidedText(const QString &text, Qt::TextElideMode mode, qreal width, int flags = 0) const; + + qreal underlinePos() const; + qreal overlinePos() const; + qreal strikeOutPos() const; + qreal lineWidth() const; + + bool operator==(const QFontMetricsF &other); // 5.0 - remove me + bool operator==(const QFontMetricsF &other) const; + inline bool operator !=(const QFontMetricsF &other) { return !operator==(other); } // 5.0 - remove me + inline bool operator !=(const QFontMetricsF &other) const { return !operator==(other); } + +private: + QExplicitlySharedDataPointer d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QFONTMETRICS_H diff --git a/src/gui/text/qfontsubset.cpp b/src/gui/text/qfontsubset.cpp new file mode 100644 index 0000000000..9dc28c35f0 --- /dev/null +++ b/src/gui/text/qfontsubset.cpp @@ -0,0 +1,1743 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include +#include "qfontsubset_p.h" +#include +#include +#include "private/qpdf_p.h" +#include "private/qfunctions_p.h" + +#ifdef Q_WS_X11 +#include "private/qfontengine_x11_p.h" +#endif + +#ifndef QT_NO_FREETYPE +#if defined(Q_WS_X11) || defined(Q_WS_QWS) +# include "private/qfontengine_ft_p.h" +#endif +#include +#include FT_FREETYPE_H +#endif + +#ifndef QT_NO_PRINTER + +QT_BEGIN_NAMESPACE + +static const char * const agl = +".notdef\0space\0exclam\0quotedbl\0numbersign\0dollar\0percent\0ampersand\0" +"quotesingle\0parenleft\0parenright\0asterisk\0plus\0comma\0hyphen\0period\0" +"slash\0zero\0one\0two\0three\0four\0five\0six\0seven\0eight\0nine\0colon\0" +"semicolon\0less\0equal\0greater\0question\0at\0A\0B\0C\0D\0E\0F\0G\0H\0I\0J\0" +"K\0L\0M\0N\0O\0P\0Q\0R\0S\0T\0U\0V\0W\0X\0Y\0Z\0bracketleft\0backslash\0" +"bracketright\0asciicircum\0underscore\0grave\0a\0b\0c\0d\0e\0f\0g\0h\0i\0j\0" +"k\0l\0m\0n\0o\0p\0q\0r\0s\0t\0u\0v\0w\0x\0y\0z\0braceleft\0bar\0braceright\0" +"asciitilde\0space\0exclamdown\0cent\0sterling\0currency\0yen\0brokenbar\0" +"section\0dieresis\0copyright\0ordfeminine\0guillemotleft\0logicalnot\0" +"hyphen\0registered\0macron\0degree\0plusminus\0twosuperior\0threesuperior\0" +"acute\0mu\0paragraph\0periodcentered\0cedilla\0onesuperior\0ordmasculine\0" +"guillemotright\0onequarter\0onehalf\0threequarters\0questiondown\0Agrave\0" +"Aacute\0Acircumflex\0Atilde\0Adieresis\0Aring\0AE\0Ccedilla\0Egrave\0Eacute\0" +"Ecircumflex\0Edieresis\0Igrave\0Iacute\0Icircumflex\0Idieresis\0Eth\0Ntilde\0" +"Ograve\0Oacute\0Ocircumflex\0Otilde\0Odieresis\0multiply\0Oslash\0Ugrave\0" +"Uacute\0Ucircumflex\0Udieresis\0Yacute\0Thorn\0germandbls\0agrave\0aacute\0" +"acircumflex\0atilde\0adieresis\0aring\0ae\0ccedilla\0egrave\0eacute\0" +"ecircumflex\0edieresis\0igrave\0iacute\0icircumflex\0idieresis\0eth\0ntilde\0" +"ograve\0oacute\0ocircumflex\0otilde\0odieresis\0divide\0oslash\0ugrave\0" +"uacute\0ucircumflex\0udieresis\0yacute\0thorn\0ydieresis\0Amacron\0amacron\0" +"Abreve\0abreve\0Aogonek\0aogonek\0Cacute\0cacute\0Ccircumflex\0ccircumflex\0" +"Cdotaccent\0cdotaccent\0Ccaron\0ccaron\0Dcaron\0dcaron\0Dcroat\0dcroat\0" +"Emacron\0emacron\0Ebreve\0ebreve\0Edotaccent\0edotaccent\0Eogonek\0eogonek\0" +"Ecaron\0ecaron\0Gcircumflex\0gcircumflex\0Gbreve\0gbreve\0Gdotaccent\0" +"gdotaccent\0Gcommaaccent\0gcommaaccent\0Hcircumflex\0hcircumflex\0Hbar\0" +"hbar\0Itilde\0itilde\0Imacron\0imacron\0Ibreve\0ibreve\0Iogonek\0iogonek\0" +"Idotaccent\0dotlessi\0IJ\0ij\0Jcircumflex\0jcircumflex\0Kcommaaccent\0" +"kcommaaccent\0kgreenlandic\0Lacute\0lacute\0Lcommaaccent\0lcommaaccent\0" +"Lcaron\0lcaron\0Ldot\0ldot\0Lslash\0lslash\0Nacute\0nacute\0Ncommaaccent\0" +"ncommaaccent\0Ncaron\0ncaron\0napostrophe\0Eng\0eng\0Omacron\0omacron\0" +"Obreve\0obreve\0Ohungarumlaut\0ohungarumlaut\0OE\0oe\0Racute\0racute\0" +"Rcommaaccent\0rcommaaccent\0Rcaron\0rcaron\0Sacute\0sacute\0Scircumflex\0" +"scircumflex\0Scedilla\0scedilla\0Scaron\0scaron\0Tcaron\0tcaron\0Tbar\0tbar\0" +"Utilde\0utilde\0Umacron\0umacron\0Ubreve\0ubreve\0Uring\0uring\0" +"Uhungarumlaut\0uhungarumlaut\0Uogonek\0uogonek\0Wcircumflex\0wcircumflex\0" +"Ycircumflex\0ycircumflex\0Ydieresis\0Zacute\0zacute\0Zdotaccent\0zdotaccent\0" +"Zcaron\0zcaron\0longs\0florin\0Ohorn\0ohorn\0Uhorn\0uhorn\0Gcaron\0gcaron\0" +"Aringacute\0aringacute\0AEacute\0aeacute\0Oslashacute\0oslashacute\0" +"Scommaaccent\0scommaaccent\0Tcommaaccent\0tcommaaccent\0afii57929\0" +"afii64937\0circumflex\0caron\0breve\0dotaccent\0ring\0ogonek\0tilde\0" +"hungarumlaut\0gravecomb\0acutecomb\0tildecomb\0hookabovecomb\0dotbelowcomb\0" +"tonos\0dieresistonos\0Alphatonos\0anoteleia\0Epsilontonos\0Etatonos\0" +"Iotatonos\0Omicrontonos\0Upsilontonos\0Omegatonos\0iotadieresistonos\0Alpha\0" +"Beta\0Gamma\0Delta\0Epsilon\0Zeta\0Eta\0Theta\0Iota\0Kappa\0Lambda\0Mu\0Nu\0" +"Xi\0Omicron\0Pi\0Rho\0Sigma\0Tau\0Upsilon\0Phi\0Chi\0Psi\0Omega\0" +"Iotadieresis\0Upsilondieresis\0alphatonos\0epsilontonos\0etatonos\0" +"iotatonos\0upsilondieresistonos\0alpha\0beta\0gamma\0delta\0epsilon\0zeta\0" +"eta\0theta\0iota\0kappa\0lambda\0mu\0nu\0xi\0omicron\0pi\0rho\0sigma1\0" +"sigma\0tau\0upsilon\0phi\0chi\0psi\0omega\0iotadieresis\0upsilondieresis\0" +; + +static const struct { quint16 u; quint16 index; } unicode_to_aglindex[] = { + {0x0000, 0}, {0x0020, 8}, {0x0021, 14}, {0x0022, 21}, + {0x0023, 30}, {0x0024, 41}, {0x0025, 48}, {0x0026, 56}, + {0x0027, 66}, {0x0028, 78}, {0x0029, 88}, {0x002A, 99}, + {0x002B, 108}, {0x002C, 113}, {0x002D, 119}, {0x002E, 126}, + {0x002F, 133}, {0x0030, 139}, {0x0031, 144}, {0x0032, 148}, + {0x0033, 152}, {0x0034, 158}, {0x0035, 163}, {0x0036, 168}, + {0x0037, 172}, {0x0038, 178}, {0x0039, 184}, {0x003A, 189}, + {0x003B, 195}, {0x003C, 205}, {0x003D, 210}, {0x003E, 216}, + {0x003F, 224}, {0x0040, 233}, {0x0041, 236}, {0x0042, 238}, + {0x0043, 240}, {0x0044, 242}, {0x0045, 244}, {0x0046, 246}, + {0x0047, 248}, {0x0048, 250}, {0x0049, 252}, {0x004A, 254}, + {0x004B, 256}, {0x004C, 258}, {0x004D, 260}, {0x004E, 262}, + {0x004F, 264}, {0x0050, 266}, {0x0051, 268}, {0x0052, 270}, + {0x0053, 272}, {0x0054, 274}, {0x0055, 276}, {0x0056, 278}, + {0x0057, 280}, {0x0058, 282}, {0x0059, 284}, {0x005A, 286}, + {0x005B, 288}, {0x005C, 300}, {0x005D, 310}, {0x005E, 323}, + {0x005F, 335}, {0x0060, 346}, {0x0061, 352}, {0x0062, 354}, + {0x0063, 356}, {0x0064, 358}, {0x0065, 360}, {0x0066, 362}, + {0x0067, 364}, {0x0068, 366}, {0x0069, 368}, {0x006A, 370}, + {0x006B, 372}, {0x006C, 374}, {0x006D, 376}, {0x006E, 378}, + {0x006F, 380}, {0x0070, 382}, {0x0071, 384}, {0x0072, 386}, + {0x0073, 388}, {0x0074, 390}, {0x0075, 392}, {0x0076, 394}, + {0x0077, 396}, {0x0078, 398}, {0x0079, 400}, {0x007A, 402}, + {0x007B, 404}, {0x007C, 414}, {0x007D, 418}, {0x007E, 429}, + {0x00A0, 440}, {0x00A1, 446}, {0x00A2, 457}, {0x00A3, 462}, + {0x00A4, 471}, {0x00A5, 480}, {0x00A6, 484}, {0x00A7, 494}, + {0x00A8, 502}, {0x00A9, 511}, {0x00AA, 521}, {0x00AB, 533}, + {0x00AC, 547}, {0x00AD, 558}, {0x00AE, 565}, {0x00AF, 576}, + {0x00B0, 583}, {0x00B1, 590}, {0x00B2, 600}, {0x00B3, 612}, + {0x00B4, 626}, {0x00B5, 632}, {0x00B6, 635}, {0x00B7, 645}, + {0x00B8, 660}, {0x00B9, 668}, {0x00BA, 680}, {0x00BB, 693}, + {0x00BC, 708}, {0x00BD, 719}, {0x00BE, 727}, {0x00BF, 741}, + {0x00C0, 754}, {0x00C1, 761}, {0x00C2, 768}, {0x00C3, 780}, + {0x00C4, 787}, {0x00C5, 797}, {0x00C6, 803}, {0x00C7, 806}, + {0x00C8, 815}, {0x00C9, 822}, {0x00CA, 829}, {0x00CB, 841}, + {0x00CC, 851}, {0x00CD, 858}, {0x00CE, 865}, {0x00CF, 877}, + {0x00D0, 887}, {0x00D1, 891}, {0x00D2, 898}, {0x00D3, 905}, + {0x00D4, 912}, {0x00D5, 924}, {0x00D6, 931}, {0x00D7, 941}, + {0x00D8, 950}, {0x00D9, 957}, {0x00DA, 964}, {0x00DB, 971}, + {0x00DC, 983}, {0x00DD, 993}, {0x00DE, 1000}, {0x00DF, 1006}, + {0x00E0, 1017}, {0x00E1, 1024}, {0x00E2, 1031}, {0x00E3, 1043}, + {0x00E4, 1050}, {0x00E5, 1060}, {0x00E6, 1066}, {0x00E7, 1069}, + {0x00E8, 1078}, {0x00E9, 1085}, {0x00EA, 1092}, {0x00EB, 1104}, + {0x00EC, 1114}, {0x00ED, 1121}, {0x00EE, 1128}, {0x00EF, 1140}, + {0x00F0, 1150}, {0x00F1, 1154}, {0x00F2, 1161}, {0x00F3, 1168}, + {0x00F4, 1175}, {0x00F5, 1187}, {0x00F6, 1194}, {0x00F7, 1204}, + {0x00F8, 1211}, {0x00F9, 1218}, {0x00FA, 1225}, {0x00FB, 1232}, + {0x00FC, 1244}, {0x00FD, 1254}, {0x00FE, 1261}, {0x00FF, 1267}, + {0x0100, 1277}, {0x0101, 1285}, {0x0102, 1293}, {0x0103, 1300}, + {0x0104, 1307}, {0x0105, 1315}, {0x0106, 1323}, {0x0107, 1330}, + {0x0108, 1337}, {0x0109, 1349}, {0x010A, 1361}, {0x010B, 1372}, + {0x010C, 1383}, {0x010D, 1390}, {0x010E, 1397}, {0x010F, 1404}, + {0x0110, 1411}, {0x0111, 1418}, {0x0112, 1425}, {0x0113, 1433}, + {0x0114, 1441}, {0x0115, 1448}, {0x0116, 1455}, {0x0117, 1466}, + {0x0118, 1477}, {0x0119, 1485}, {0x011A, 1493}, {0x011B, 1500}, + {0x011C, 1507}, {0x011D, 1519}, {0x011E, 1531}, {0x011F, 1538}, + {0x0120, 1545}, {0x0121, 1556}, {0x0122, 1567}, {0x0123, 1580}, + {0x0124, 1593}, {0x0125, 1605}, {0x0126, 1617}, {0x0127, 1622}, + {0x0128, 1627}, {0x0129, 1634}, {0x012A, 1641}, {0x012B, 1649}, + {0x012C, 1657}, {0x012D, 1664}, {0x012E, 1671}, {0x012F, 1679}, + {0x0130, 1687}, {0x0131, 1698}, {0x0132, 1707}, {0x0133, 1710}, + {0x0134, 1713}, {0x0135, 1725}, {0x0136, 1737}, {0x0137, 1750}, + {0x0138, 1763}, {0x0139, 1776}, {0x013A, 1783}, {0x013B, 1790}, + {0x013C, 1803}, {0x013D, 1816}, {0x013E, 1823}, {0x013F, 1830}, + {0x0140, 1835}, {0x0141, 1840}, {0x0142, 1847}, {0x0143, 1854}, + {0x0144, 1861}, {0x0145, 1868}, {0x0146, 1881}, {0x0147, 1894}, + {0x0148, 1901}, {0x0149, 1908}, {0x014A, 1920}, {0x014B, 1924}, + {0x014C, 1928}, {0x014D, 1936}, {0x014E, 1944}, {0x014F, 1951}, + {0x0150, 1958}, {0x0151, 1972}, {0x0152, 1986}, {0x0153, 1989}, + {0x0154, 1992}, {0x0155, 1999}, {0x0156, 2006}, {0x0157, 2019}, + {0x0158, 2032}, {0x0159, 2039}, {0x015A, 2046}, {0x015B, 2053}, + {0x015C, 2060}, {0x015D, 2072}, {0x015E, 2084}, {0x015F, 2093}, + {0x0160, 2102}, {0x0161, 2109}, {0x0164, 2116}, {0x0165, 2123}, + {0x0166, 2130}, {0x0167, 2135}, {0x0168, 2140}, {0x0169, 2147}, + {0x016A, 2154}, {0x016B, 2162}, {0x016C, 2170}, {0x016D, 2177}, + {0x016E, 2184}, {0x016F, 2190}, {0x0170, 2196}, {0x0171, 2210}, + {0x0172, 2224}, {0x0173, 2232}, {0x0174, 2240}, {0x0175, 2252}, + {0x0176, 2264}, {0x0177, 2276}, {0x0178, 2288}, {0x0179, 2298}, + {0x017A, 2305}, {0x017B, 2312}, {0x017C, 2323}, {0x017D, 2334}, + {0x017E, 2341}, {0x017F, 2348}, {0x0192, 2354}, {0x01A0, 2361}, + {0x01A1, 2367}, {0x01AF, 2373}, {0x01B0, 2379}, {0x01E6, 2385}, + {0x01E7, 2392}, {0x01FA, 2399}, {0x01FB, 2410}, {0x01FC, 2421}, + {0x01FD, 2429}, {0x01FE, 2437}, {0x01FF, 2449}, {0x0218, 2461}, + {0x0219, 2474}, {0x021A, 2487}, {0x021B, 2500}, {0x02BC, 2513}, + {0x02BD, 2523}, {0x02C6, 2533}, {0x02C7, 2544}, {0x02D8, 2550}, + {0x02D9, 2556}, {0x02DA, 2566}, {0x02DB, 2571}, {0x02DC, 2578}, + {0x02DD, 2584}, {0x0300, 2597}, {0x0301, 2607}, {0x0303, 2617}, + {0x0309, 2627}, {0x0323, 2641}, {0x0384, 2654}, {0x0385, 2660}, + {0x0386, 2674}, {0x0387, 2685}, {0x0388, 2695}, {0x0389, 2708}, + {0x038A, 2717}, {0x038C, 2727}, {0x038E, 2740}, {0x038F, 2753}, + {0x0390, 2764}, {0x0391, 2782}, {0x0392, 2788}, {0x0393, 2793}, + {0x0394, 2799}, {0x0395, 2805}, {0x0396, 2813}, {0x0397, 2818}, + {0x0398, 2822}, {0x0399, 2828}, {0x039A, 2833}, {0x039B, 2839}, + {0x039C, 2846}, {0x039D, 2849}, {0x039E, 2852}, {0x039F, 2855}, + {0x03A0, 2863}, {0x03A1, 2866}, {0x03A3, 2870}, {0x03A4, 2876}, + {0x03A5, 2880}, {0x03A6, 2888}, {0x03A7, 2892}, {0x03A8, 2896}, + {0x03A9, 2900}, {0x03AA, 2906}, {0x03AB, 2919}, {0x03AC, 2935}, + {0x03AD, 2946}, {0x03AE, 2959}, {0x03AF, 2968}, {0x03B0, 2978}, + {0x03B1, 2999}, {0x03B2, 3005}, {0x03B3, 3010}, {0x03B4, 3016}, + {0x03B5, 3022}, {0x03B6, 3030}, {0x03B7, 3035}, {0x03B8, 3039}, + {0x03B9, 3045}, {0x03BA, 3050}, {0x03BB, 3056}, {0x03BC, 3063}, + {0x03BD, 3066}, {0x03BE, 3069}, {0x03BF, 3072}, {0x03C0, 3080}, + {0x03C1, 3083}, {0x03C2, 3087}, {0x03C3, 3094}, {0x03C4, 3100}, + {0x03C5, 3104}, {0x03C6, 3112}, {0x03C7, 3116}, {0x03C8, 3120}, + {0x03C9, 3124}, {0x03CA, 3130}, {0x03CB, 3143}, {0x03CC, 3159}, + {0x03CD, 3172}, {0x03CE, 3185}, {0x03D1, 3196}, {0x03D2, 3203}, + {0x03D5, 3212}, {0x03D6, 3217}, {0xFFFF, 3224} +}; + +// This map is used for symbol fonts to get the correct glyph names for the latin range +static const unsigned short symbol_map[0x100] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x2200, 0x0023, 0x2203, 0x0025, 0x0026, 0x220b, + 0x0028, 0x0029, 0x2217, 0x002b, 0x002c, 0x2212, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + + 0x2245, 0x0391, 0x0392, 0x03a7, 0x0394, 0x0395, 0x03a6, 0x0393, + 0x0397, 0x0399, 0x03d1, 0x039a, 0x039b, 0x039c, 0x039d, 0x039f, + 0x03a0, 0x0398, 0x03a1, 0x03a3, 0x03a4, 0x03a5, 0x03c2, 0x03a9, + 0x039e, 0x03a8, 0x0396, 0x005b, 0x2234, 0x005d, 0x22a5, 0x005f, + 0xf8e5, 0x03b1, 0x03b2, 0x03c7, 0x03b4, 0x03b5, 0x03c6, 0x03b3, + 0x03b7, 0x03b9, 0x03d5, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03bf, + 0x03c0, 0x03b8, 0x03c1, 0x03c3, 0x03c4, 0x03c5, 0x03d6, 0x03c9, + 0x03be, 0x03c8, 0x03b6, 0x007b, 0x007c, 0x007d, 0x223c, 0x007f, + + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x20ac, 0x03d2, 0x2023, 0x2264, 0x2044, 0x221e, 0x0192, 0x2263, + 0x2666, 0x2665, 0x2660, 0x2194, 0x2190, 0x2191, 0x2192, 0x2193, + 0x00b0, 0x00b1, 0x2033, 0x2265, 0x00d7, 0x221d, 0x2202, 0x2022, + 0x00f7, 0x2260, 0x2261, 0x2248, 0x2026, 0xf8e6, 0xf8e7, 0x21b5, + + 0x2135, 0x2111, 0x211c, 0x2118, 0x2297, 0x2295, 0x2205, 0x2229, + 0x222a, 0x2283, 0x2287, 0x2284, 0x2282, 0x2286, 0x2208, 0x2209, + 0x2220, 0x2207, 0xf6da, 0xf6d9, 0xf6db, 0x220f, 0x221a, 0x22c5, + 0x00ac, 0x2227, 0x2228, 0x21d4, 0x21d0, 0x21d1, 0x21d2, 0x21d3, + 0x25ca, 0x2329, 0xf8e8, 0xf8e9, 0xf8ea, 0x2211, 0xf8eb, 0xf8ec, + 0xf8ed, 0xf8ee, 0xf8ef, 0xf8f0, 0xf8f1, 0xf8f2, 0xf8f3, 0xf8f4, + 0x0000, 0x232a, 0x222b, 0x2320, 0xf8f5, 0x2321, 0xf8f6, 0xf8f7, + 0xf8f8, 0xf8f9, 0xf8fa, 0xf8fb, 0xf8fc, 0xf8fd, 0xf8fe, 0x0000, +}; + +// ---------------------------- PS/PDF helper methods ----------------------------------- + +QByteArray QFontSubset::glyphName(unsigned short unicode, bool symbol) +{ + if (symbol && unicode < 0x100) + // map from latin1 to symbol + unicode = symbol_map[unicode]; + + int l = 0; + while(unicode_to_aglindex[l].u < unicode) + l++; + if (unicode_to_aglindex[l].u == unicode) + return agl + unicode_to_aglindex[l].index; + + char buffer[8]; + buffer[0] = 'u'; + buffer[1] = 'n'; + buffer[2] = 'i'; + QPdf::toHex(unicode, buffer+3); + return buffer; +} + +#ifndef QT_NO_FREETYPE +static FT_Face ft_face(const QFontEngine *engine) +{ +#ifdef Q_WS_X11 +#ifndef QT_NO_FONTCONFIG + if (engine->type() == QFontEngine::Freetype) { + const QFontEngineFT *ft = static_cast(engine); + return ft->non_locked_face(); + } else +#endif + if (engine->type() == QFontEngine::XLFD) { + const QFontEngineXLFD *xlfd = static_cast(engine); + return xlfd->non_locked_face(); + } +#endif +#ifdef Q_WS_QWS + if (engine->type() == QFontEngine::Freetype) { + const QFontEngineFT *ft = static_cast(engine); + return ft->non_locked_face(); + } +#endif + return 0; +} +#endif + +QByteArray QFontSubset::glyphName(unsigned int glyph, const QVector reverseMap) const +{ + uint glyphIndex = glyph_indices[glyph]; + + if (glyphIndex == 0) + return "/.notdef"; + + QByteArray ba; + QPdf::ByteStream s(&ba); +#ifndef QT_NO_FREETYPE + FT_Face face = ft_face(fontEngine); + + char name[32]; + name[0] = 0; + if (face && FT_HAS_GLYPH_NAMES(face)) { +#if defined(Q_WS_X11) + if (fontEngine->type() == QFontEngine::XLFD) + glyphIndex = static_cast(fontEngine)->glyphIndexToFreetypeGlyphIndex(glyphIndex); +#endif + FT_Get_Glyph_Name(face, glyphIndex, &name, 32); + if (name[0] == '.') // fix broken PS fonts returning .notdef for many glyphs + name[0] = 0; + } + if (name[0]) { + s << '/' << name; + } else +#endif +#if defined(Q_WS_X11) + if (fontEngine->type() == QFontEngine::XLFD) { + uint uc = static_cast(fontEngine)->toUnicode(glyphIndex); + s << '/' << glyphName(uc, false /* ### */); + } else +#endif + if (reverseMap[glyphIndex] && reverseMap[glyphIndex] < 0x10000) { + s << '/' << glyphName(reverseMap[glyphIndex], false); + } else { + s << "/gl" << (int)glyphIndex; + } + return ba; +} + + +QByteArray QFontSubset::widthArray() const +{ + Q_ASSERT(!widths.isEmpty()); + + QFontEngine::Properties properties = fontEngine->properties(); + + QByteArray width; + QPdf::ByteStream s(&width); + QFixed scale = QFixed(1000)/emSquare; + + QFixed defWidth = widths[0]; + //qDebug("defWidth=%d, scale=%f", defWidth.toInt(), scale.toReal()); + for (int i = 0; i < nGlyphs(); ++i) { + if (defWidth != widths[i]) + defWidth = 0; + } + if (defWidth > 0) { + s << "/DW " << (defWidth*scale).toInt(); + } else { + s << "/W ["; + for (int g = 0; g < nGlyphs();) { + QFixed w = widths[g]; + int start = g; + int startLinear = 0; + ++g; + while (g < nGlyphs()) { + QFixed nw = widths[g]; + if (nw == w) { + if (!startLinear) + startLinear = g - 1; + } else { + if (startLinear > 0 && g - startLinear >= 10) + break; + startLinear = 0; + } + w = nw; + ++g; + } + // qDebug("start=%x startLinear=%x g-1=%x",start,startLinear,g-1); + if (g - startLinear < 10) + startLinear = 0; + int endnonlinear = startLinear ? startLinear : g; + // qDebug(" startLinear=%x endnonlinear=%x", startLinear,endnonlinear); + if (endnonlinear > start) { + s << start << '['; + for (int i = start; i < endnonlinear; ++i) + s << (widths[i]*scale).toInt(); + s << "]\n"; + } + if (startLinear) + s << startLinear << g - 1 << (widths[startLinear]*scale).toInt() << '\n'; + } + s << "]\n"; + } + return width; +} + +static void checkRanges(QPdf::ByteStream &ts, QByteArray &ranges, int &nranges) +{ + if (++nranges > 100) { + ts << nranges << "beginbfrange\n" + << ranges << "endbfrange\n"; + ranges = QByteArray(); + nranges = 0; + } +} + +QVector QFontSubset::getReverseMap() const +{ + QVector reverseMap; + reverseMap.resize(0x10000); + for (uint i = 0; i < 0x10000; ++i) + reverseMap[i] = 0; + QGlyphLayoutArray<10> glyphs; + for (uint uc = 0; uc < 0x10000; ++uc) { + QChar ch(uc); + int nglyphs = 10; + fontEngine->stringToCMap(&ch, 1, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly); + int idx = glyph_indices.indexOf(glyphs.glyphs[0]); + if (idx >= 0 && !reverseMap.at(idx)) + reverseMap[idx] = uc; + } + return reverseMap; +} + +QByteArray QFontSubset::createToUnicodeMap() const +{ + QVector reverseMap = getReverseMap(); + + QByteArray touc; + QPdf::ByteStream ts(&touc); + ts << "/CIDInit /ProcSet findresource begin\n" + "12 dict begin\n" + "begincmap\n" + "/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n" + "/CMapName /Adobe-Identity-UCS def\n" + "/CMapType 2 def\n" + "1 begincodespacerange\n" + "<0000> \n" + "endcodespacerange\n"; + + int nranges = 1; + QByteArray ranges = "<0000> <0000> <0000>\n"; + QPdf::ByteStream s(&ranges); + + char buf[5]; + for (int g = 1; g < nGlyphs(); ) { + int uc0 = reverseMap.at(g); + if (!uc0) { + ++g; + continue; + } + int start = g; + int startLinear = 0; + ++g; + while (g < nGlyphs()) { + int uc = reverseMap[g]; + // cmaps can't have the high byte changing within one range, so we need to break on that as well + if (!uc || (g>>8) != (start >> 8)) + break; + if (uc == uc0 + 1) { + if (!startLinear) + startLinear = g - 1; + } else { + if (startLinear > 0 && g - startLinear >= 10) + break; + startLinear = 0; + } + uc0 = uc; + ++g; + } + // qDebug("start=%x startLinear=%x g-1=%x",start,startLinear,g-1); + if (g - startLinear < 10) + startLinear = 0; + int endnonlinear = startLinear ? startLinear : g; + // qDebug(" startLinear=%x endnonlinear=%x", startLinear,endnonlinear); + if (endnonlinear > start) { + s << '<' << QPdf::toHex((ushort)start, buf) << "> <"; + s << QPdf::toHex((ushort)(endnonlinear - 1), buf) << "> "; + if (endnonlinear == start + 1) { + s << '<' << QPdf::toHex((ushort)reverseMap[start], buf) << ">\n"; + } else { + s << '['; + for (int i = start; i < endnonlinear; ++i) { + s << '<' << QPdf::toHex((ushort)reverseMap[i], buf) << "> "; + } + s << "]\n"; + } + checkRanges(ts, ranges, nranges); + } + if (startLinear) { + while (startLinear < g) { + int len = g - startLinear; + int uc_start = reverseMap[startLinear]; + int uc_end = uc_start + len - 1; + if ((uc_end >> 8) != (uc_start >> 8)) + len = 256 - (uc_start & 0xff); + s << '<' << QPdf::toHex((ushort)startLinear, buf) << "> <"; + s << QPdf::toHex((ushort)(startLinear + len - 1), buf) << "> "; + s << '<' << QPdf::toHex((ushort)reverseMap[startLinear], buf) << ">\n"; + checkRanges(ts, ranges, nranges); + startLinear += len; + } + } + } + if (nranges) { + ts << nranges << "beginbfrange\n" + << ranges << "endbfrange\n"; + } + ts << "endcmap\n" + "CMapName currentdict /CMap defineresource pop\n" + "end\n" + "end\n"; + + return touc; +} + +int QFontSubset::addGlyph(int index) +{ + int idx = glyph_indices.indexOf(index); + if (idx < 0) { + idx = glyph_indices.size(); + glyph_indices.append(index); + } + return idx; +} + + +// ------------------------------ Truetype generation ---------------------------------------------- + +typedef qint16 F2DOT14; +typedef quint32 Tag; +typedef quint16 GlyphID; +typedef quint16 Offset; + + +class QTtfStream { +public: + QTtfStream(QByteArray &ba) : data((uchar *)ba.data()) { start = data; } + QTtfStream &operator <<(quint8 v) { *data = v; ++data; return *this; } + QTtfStream &operator <<(quint16 v) { qToBigEndian(v, data); data += sizeof(v); return *this; } + QTtfStream &operator <<(quint32 v) { qToBigEndian(v, data); data += sizeof(v); return *this; } + QTtfStream &operator <<(qint8 v) { *data = quint8(v); ++data; return *this; } + QTtfStream &operator <<(qint16 v) { qToBigEndian(v, data); data += sizeof(v); return *this; } + QTtfStream &operator <<(qint32 v) { qToBigEndian(v, data); data += sizeof(v); return *this; } + QTtfStream &operator <<(qint64 v) { qToBigEndian(v, data); data += sizeof(v); return *this; } + + int offset() const { return data - start; } + void setOffset(int o) { data = start + o; } + void align4() { while (offset() & 3) { *data = '\0'; ++data; } } +private: + uchar *data; + uchar *start; +}; + +struct QTtfTable { + Tag tag; + QByteArray data; +}; +Q_DECLARE_TYPEINFO(QTtfTable, Q_MOVABLE_TYPE); + + +struct qttf_head_table { + qint32 font_revision; + quint16 flags; + qint64 created; + qint64 modified; + qint16 xMin; + qint16 yMin; + qint16 xMax; + qint16 yMax; + quint16 macStyle; + qint16 indexToLocFormat; +}; + + +struct qttf_hhea_table { + qint16 ascender; + qint16 descender; + qint16 lineGap; + quint16 maxAdvanceWidth; + qint16 minLeftSideBearing; + qint16 minRightSideBearing; + qint16 xMaxExtent; + quint16 numberOfHMetrics; +}; + + +struct qttf_maxp_table { + quint16 numGlyphs; + quint16 maxPoints; + quint16 maxContours; + quint16 maxCompositePoints; + quint16 maxCompositeContours; + quint16 maxComponentElements; + quint16 maxComponentDepth; +}; + +struct qttf_name_table { + QString copyright; + QString family; + QString subfamily; + QString postscript_name; +}; + + +static QTtfTable generateHead(const qttf_head_table &head); +static QTtfTable generateHhea(const qttf_hhea_table &hhea); +static QTtfTable generateMaxp(const qttf_maxp_table &maxp); +static QTtfTable generateName(const qttf_name_table &name); + +struct qttf_font_tables +{ + qttf_head_table head; + qttf_hhea_table hhea; + qttf_maxp_table maxp; +}; + + +struct QTtfGlyph { + quint16 index; + qint16 xMin; + qint16 xMax; + qint16 yMin; + qint16 yMax; + quint16 advanceWidth; + qint16 lsb; + quint16 numContours; + quint16 numPoints; + QByteArray data; +}; +Q_DECLARE_TYPEINFO(QTtfGlyph, Q_MOVABLE_TYPE); + +static QTtfGlyph generateGlyph(int index, const QPainterPath &path, qreal advance, qreal lsb, qreal ppem); +// generates glyf, loca and hmtx +static QList generateGlyphTables(qttf_font_tables &tables, const QList &_glyphs); + +static QByteArray bindFont(const QList& _tables); + + +static quint32 checksum(const QByteArray &table) +{ + quint32 sum = 0; + int offset = 0; + const uchar *d = (uchar *)table.constData(); + while (offset <= table.size()-3) { + sum += qFromBigEndian(d + offset); + offset += 4; + } + int shift = 24; + quint32 x = 0; + while (offset < table.size()) { + x |= ((quint32)d[offset]) << shift; + ++offset; + shift -= 8; + } + sum += x; + + return sum; +} + +static QTtfTable generateHead(const qttf_head_table &head) +{ + const int head_size = 54; + QTtfTable t; + t.tag = MAKE_TAG('h', 'e', 'a', 'd'); + t.data.resize(head_size); + + QTtfStream s(t.data); + +// qint32 Table version number 0x00010000 for version 1.0. +// qint32 fontRevision Set by font manufacturer. + s << qint32(0x00010000) + << head.font_revision +// quint32 checkSumAdjustment To compute: set it to 0, sum the entire font as quint32, then store 0xB1B0AFBA - sum. + << quint32(0) +// quint32 magicNumber Set to 0x5F0F3CF5. + << quint32(0x5F0F3CF5) +// quint16 flags Bit 0: Baseline for font at y=0; +// Bit 1: Left sidebearing point at x=0; +// Bit 2: Instructions may depend on point size; +// Bit 3: Force ppem to integer values for all internal scaler math; may use fractional ppem sizes if this bit is clear; +// Bit 4: Instructions may alter advance width (the advance widths might not scale linearly); +// Bits 5-10: These should be set according to Apple's specification . However, they are not implemented in OpenType. +// Bit 11: Font data is 'lossless,' as a result of having been compressed and decompressed with the Agfa MicroType Express engine. +// Bit 12: Font converted (produce compatible metrics) +// Bit 13: Font optimized for ClearType +// Bit 14: Reserved, set to 0 +// Bit 15: Reserved, set to 0 + << quint16(0) + +// quint16 unitsPerEm Valid range is from 16 to 16384. This value should be a power of 2 for fonts that have TrueType outlines. + << quint16(2048) +// qint64 created Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer + << head.created +// qint64 modified Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer + << head.modified +// qint16 xMin For all glyph bounding boxes. +// qint16 yMin For all glyph bounding boxes. +// qint16 xMax For all glyph bounding boxes. +// qint16 yMax For all glyph bounding boxes. + << head.xMin + << head.yMin + << head.xMax + << head.yMax +// quint16 macStyle Bit 0: Bold (if set to 1); +// Bit 1: Italic (if set to 1) +// Bit 2: Underline (if set to 1) +// Bit 3: Outline (if set to 1) +// Bit 4: Shadow (if set to 1) +// Bit 5: Condensed (if set to 1) +// Bit 6: Extended (if set to 1) +// Bits 7-15: Reserved (set to 0). + << head.macStyle +// quint16 lowestRecPPEM Smallest readable size in pixels. + << quint16(6) // just a wild guess +// qint16 fontDirectionHint 0: Fully mixed directional glyphs; + << qint16(0) +// 1: Only strongly left to right; +// 2: Like 1 but also contains neutrals; +// -1: Only strongly right to left; +// -2: Like -1 but also contains neutrals. 1 +// qint16 indexToLocFormat 0 for short offsets, 1 for long. + << head.indexToLocFormat +// qint16 glyphDataFormat 0 for current format. + << qint16(0); + + Q_ASSERT(s.offset() == head_size); + return t; +} + + +static QTtfTable generateHhea(const qttf_hhea_table &hhea) +{ + const int hhea_size = 36; + QTtfTable t; + t.tag = MAKE_TAG('h', 'h', 'e', 'a'); + t.data.resize(hhea_size); + + QTtfStream s(t.data); +// qint32 Table version number 0x00010000 for version 1.0. + s << qint32(0x00010000) +// qint16 Ascender Typographic ascent. (Distance from baseline of highest ascender) + << hhea.ascender +// qint16 Descender Typographic descent. (Distance from baseline of lowest descender) + << hhea.descender +// qint16 LineGap Typographic line gap. +// Negative LineGap values are treated as zero +// in Windows 3.1, System 6, and +// System 7. + << hhea.lineGap +// quint16 advanceWidthMax Maximum advance width value in 'hmtx' table. + << hhea.maxAdvanceWidth +// qint16 minLeftSideBearing Minimum left sidebearing value in 'hmtx' table. + << hhea.minLeftSideBearing +// qint16 minRightSideBearing Minimum right sidebearing value; calculated as Min(aw - lsb - (xMax - xMin)). + << hhea.minRightSideBearing +// qint16 xMaxExtent Max(lsb + (xMax - xMin)). + << hhea.xMaxExtent +// qint16 caretSlopeRise Used to calculate the slope of the cursor (rise/run); 1 for vertical. + << qint16(1) +// qint16 caretSlopeRun 0 for vertical. + << qint16(0) +// qint16 caretOffset The amount by which a slanted highlight on a glyph needs to be shifted to produce the best appearance. Set to 0 for non-slanted fonts + << qint16(0) +// qint16 (reserved) set to 0 + << qint16(0) +// qint16 (reserved) set to 0 + << qint16(0) +// qint16 (reserved) set to 0 + << qint16(0) +// qint16 (reserved) set to 0 + << qint16(0) +// qint16 metricDataFormat 0 for current format. + << qint16(0) +// quint16 numberOfHMetrics Number of hMetric entries in 'hmtx' table + << hhea.numberOfHMetrics; + + Q_ASSERT(s.offset() == hhea_size); + return t; +} + + +static QTtfTable generateMaxp(const qttf_maxp_table &maxp) +{ + const int maxp_size = 32; + QTtfTable t; + t.tag = MAKE_TAG('m', 'a', 'x', 'p'); + t.data.resize(maxp_size); + + QTtfStream s(t.data); + +// qint32 Table version number 0x00010000 for version 1.0. + s << qint32(0x00010000) +// quint16 numGlyphs The number of glyphs in the font. + << maxp.numGlyphs +// quint16 maxPoints Maximum points in a non-composite glyph. + << maxp.maxPoints +// quint16 maxContours Maximum contours in a non-composite glyph. + << maxp.maxContours +// quint16 maxCompositePoints Maximum points in a composite glyph. + << maxp.maxCompositePoints +// quint16 maxCompositeContours Maximum contours in a composite glyph. + << maxp.maxCompositeContours +// quint16 maxZones 1 if instructions do not use the twilight zone (Z0), or 2 if instructions do use Z0; should be set to 2 in most cases. + << quint16(1) // we do not embed instructions +// quint16 maxTwilightPoints Maximum points used in Z0. + << quint16(0) +// quint16 maxStorage Number of Storage Area locations. + << quint16(0) +// quint16 maxFunctionDefs Number of FDEFs. + << quint16(0) +// quint16 maxInstructionDefs Number of IDEFs. + << quint16(0) +// quint16 maxStackElements Maximum stack depth2. + << quint16(0) +// quint16 maxSizeOfInstructions Maximum byte count for glyph instructions. + << quint16(0) +// quint16 maxComponentElements Maximum number of components referenced at "top level" for any composite glyph. + << maxp.maxComponentElements +// quint16 maxComponentDepth Maximum levels of recursion; 1 for simple components. + << maxp.maxComponentDepth; + + Q_ASSERT(s.offset() == maxp_size); + return t; +} + +struct QTtfNameRecord { + quint16 nameId; + QString value; +}; + +static QTtfTable generateName(const QList &name); + +static QTtfTable generateName(const qttf_name_table &name) +{ + QList list; + QTtfNameRecord rec; + rec.nameId = 0; + rec.value = name.copyright; + list.append(rec); + rec.nameId = 1; + rec.value = name.family; + list.append(rec); + rec.nameId = 2; + rec.value = name.subfamily; + list.append(rec); + rec.nameId = 4; + rec.value = name.family; + if (name.subfamily != QLatin1String("Regular")) + rec.value += QLatin1Char(' ') + name.subfamily; + list.append(rec); + rec.nameId = 6; + rec.value = name.postscript_name; + list.append(rec); + + return generateName(list); +} + +// ####### should probably generate Macintosh/Roman name entries as well +static QTtfTable generateName(const QList &name) +{ + const int char_size = 2; + + QTtfTable t; + t.tag = MAKE_TAG('n', 'a', 'm', 'e'); + + const int name_size = 6 + 12*name.size(); + int string_size = 0; + for (int i = 0; i < name.size(); ++i) { + string_size += name.at(i).value.length()*char_size; + } + t.data.resize(name_size + string_size); + + QTtfStream s(t.data); +// quint16 format Format selector (=0). + s << quint16(0) +// quint16 count Number of name records. + << quint16(name.size()) +// quint16 stringOffset Offset to start of string storage (from start of table). + << quint16(name_size); +// NameRecord nameRecord[count] The name records where count is the number of records. +// (Variable) + + int off = 0; + for (int i = 0; i < name.size(); ++i) { + int len = name.at(i).value.length()*char_size; +// quint16 platformID Platform ID. +// quint16 encodingID Platform-specific encoding ID. +// quint16 languageID Language ID. + s << quint16(3) + << quint16(1) + << quint16(0x0409) // en_US +// quint16 nameId Name ID. + << name.at(i).nameId +// quint16 length String length (in bytes). + << quint16(len) +// quint16 offset String offset from start of storage area (in bytes). + << quint16(off); + off += len; + } + for (int i = 0; i < name.size(); ++i) { + const QString &n = name.at(i).value; + const ushort *uc = n.utf16(); + for (int i = 0; i < n.length(); ++i) { + s << quint16(*uc); + ++uc; + } + } + return t; +} + + +enum Flags { + OffCurve = 0, + OnCurve = (1 << 0), + XShortVector = (1 << 1), + YShortVector = (1 << 2), + Repeat = (1 << 3), + XSame = (1 << 4), + XShortPositive = (1 << 4), + YSame = (1 << 5), + YShortPositive = (1 << 5) +}; +struct TTF_POINT { + qint16 x; + qint16 y; + quint8 flags; +}; +Q_DECLARE_TYPEINFO(TTF_POINT, Q_PRIMITIVE_TYPE); + +static void convertPath(const QPainterPath &path, QList *points, QList *endPoints, qreal ppem) +{ + int numElements = path.elementCount(); + for (int i = 0; i < numElements - 1; ++i) { + const QPainterPath::Element &e = path.elementAt(i); + TTF_POINT p; + p.x = qRound(e.x * 2048. / ppem); + p.y = qRound(-e.y * 2048. / ppem); + p.flags = 0; + + switch(e.type) { + case QPainterPath::MoveToElement: + if (i != 0) { + // see if start and end points of the last contour agree + int start = endPoints->size() ? endPoints->at(endPoints->size()-1) - 1 : 0; + int end = points->size() - 1; + if (points->at(end).x == points->at(start).x + && points->at(end).y == points->at(start).y) + points->takeLast(); + endPoints->append(points->size() - 1); + } + // fall through + case QPainterPath::LineToElement: + p.flags = OnCurve; + break; + case QPainterPath::CurveToElement: { + // cubic bezier curve, we need to reduce to a list of quadratic curves + TTF_POINT list[3*16 + 4]; // we need max 16 subdivisions + list[3] = points->at(points->size() - 1); + list[2] = p; + const QPainterPath::Element &e2 = path.elementAt(++i); + list[1].x = qRound(e2.x * 2048. / ppem); + list[1].y = qRound(-e2.y * 2048. / ppem); + const QPainterPath::Element &e3 = path.elementAt(++i); + list[0].x = qRound(e3.x * 2048. / ppem); + list[0].y = qRound(-e3.y * 2048. / ppem); + + TTF_POINT *base = list; + + bool try_reduce = points->size() > 1 + && points->at(points->size() - 1).flags == OnCurve + && points->at(points->size() - 2).flags == OffCurve; +// qDebug("generating beziers:"); + while (base >= list) { + const int split_limit = 3; +// { +// qDebug("iteration:"); +// TTF_POINT *x = list; +// while (x <= base + 3) { +// qDebug() << " " << QPoint(x->x, x->y); +// ++x; +// } +// } + Q_ASSERT(base - list < 3*16 + 1); + // first see if we can easily reduce the cubic to a quadratic bezier curve + int i1_x = base[1].x + ((base[1].x - base[0].x) >> 1); + int i1_y = base[1].y + ((base[1].y - base[0].y) >> 1); + int i2_x = base[2].x + ((base[2].x - base[3].x) >> 1); + int i2_y = base[2].y + ((base[2].y - base[3].y) >> 1); +// qDebug() << "checking: i1=" << QPoint(i1_x, i1_y) << " i2=" << QPoint(i2_x, i2_y); + if (qAbs(i1_x - i2_x) <= split_limit && qAbs(i1_y - i2_y) <= split_limit) { + // got a quadratic bezier curve + TTF_POINT np; + np.x = (i1_x + i2_x) >> 1; + np.y = (i1_y + i2_y) >> 1; + if (try_reduce) { + // see if we can optimize out the last onCurve point + int mx = (points->at(points->size() - 2).x + base[2].x) >> 1; + int my = (points->at(points->size() - 2).y + base[2].y) >> 1; + if (qAbs(mx - base[3].x) <= split_limit && qAbs(my = base[3].y) <= split_limit) + points->takeLast(); + try_reduce = false; + } + np.flags = OffCurve; + points->append(np); +// qDebug() << " appending offcurve point " << QPoint(np.x, np.y); + base -= 3; + } else { + // need to split +// qDebug() << " -> splitting"; + qint16 a, b, c, d; + base[6].x = base[3].x; + c = base[1].x; + d = base[2].x; + base[1].x = a = ( base[0].x + c ) >> 1; + base[5].x = b = ( base[3].x + d ) >> 1; + c = ( c + d ) >> 1; + base[2].x = a = ( a + c ) >> 1; + base[4].x = b = ( b + c ) >> 1; + base[3].x = ( a + b ) >> 1; + + base[6].y = base[3].y; + c = base[1].y; + d = base[2].y; + base[1].y = a = ( base[0].y + c ) >> 1; + base[5].y = b = ( base[3].y + d ) >> 1; + c = ( c + d ) >> 1; + base[2].y = a = ( a + c ) >> 1; + base[4].y = b = ( b + c ) >> 1; + base[3].y = ( a + b ) >> 1; + base += 3; + } + } + p = list[0]; + p.flags = OnCurve; + break; + } + case QPainterPath::CurveToDataElement: + Q_ASSERT(false); + break; + } +// qDebug() << " appending oncurve point " << QPoint(p.x, p.y); + points->append(p); + } + int start = endPoints->size() ? endPoints->at(endPoints->size()-1) + 1 : 0; + int end = points->size() - 1; + if (points->at(end).x == points->at(start).x + && points->at(end).y == points->at(start).y) + points->takeLast(); + endPoints->append(points->size() - 1); +} + +static void getBounds(const QList &points, qint16 *xmin, qint16 *xmax, qint16 *ymin, qint16 *ymax) +{ + *xmin = points.at(0).x; + *xmax = *xmin; + *ymin = points.at(0).y; + *ymax = *ymin; + + for (int i = 1; i < points.size(); ++i) { + *xmin = qMin(*xmin, points.at(i).x); + *xmax = qMax(*xmax, points.at(i).x); + *ymin = qMin(*ymin, points.at(i).y); + *ymax = qMax(*ymax, points.at(i).y); + } +} + +static int convertToRelative(QList *points) +{ + // convert points to relative and setup flags +// qDebug() << "relative points:"; + qint16 prev_x = 0; + qint16 prev_y = 0; + int point_array_size = 0; + for (int i = 0; i < points->size(); ++i) { + const int x = points->at(i).x; + const int y = points->at(i).y; + TTF_POINT rel; + rel.x = x - prev_x; + rel.y = y - prev_y; + rel.flags = points->at(i).flags; + Q_ASSERT(rel.flags < 2); + if (!rel.x) { + rel.flags |= XSame; + } else if (rel.x > 0 && rel.x < 256) { + rel.flags |= XShortVector|XShortPositive; + point_array_size++; + } else if (rel.x < 0 && rel.x > -256) { + rel.flags |= XShortVector; + rel.x = -rel.x; + point_array_size++; + } else { + point_array_size += 2; + } + if (!rel.y) { + rel.flags |= YSame; + } else if (rel.y > 0 && rel.y < 256) { + rel.flags |= YShortVector|YShortPositive; + point_array_size++; + } else if (rel.y < 0 && rel.y > -256) { + rel.flags |= YShortVector; + rel.y = -rel.y; + point_array_size++; + } else { + point_array_size += 2; + } + (*points)[i] = rel; +// #define toString(x) ((rel.flags & x) ? #x : "") +// qDebug() << " " << QPoint(rel.x, rel.y) << "flags=" +// << toString(OnCurve) << toString(XShortVector) +// << (rel.flags & XShortVector ? toString(XShortPositive) : toString(XSame)) +// << toString(YShortVector) +// << (rel.flags & YShortVector ? toString(YShortPositive) : toString(YSame)); + + prev_x = x; + prev_y = y; + } + return point_array_size; +} + +static void getGlyphData(QTtfGlyph *glyph, const QList &points, const QList &endPoints, int point_array_size) +{ + const int max_size = 5*sizeof(qint16) // header + + endPoints.size()*sizeof(quint16) // end points of contours + + sizeof(quint16) // instruction length == 0 + + points.size()*(1) // flags + + point_array_size; // coordinates + + glyph->data.resize(max_size); + + QTtfStream s(glyph->data); + s << qint16(endPoints.size()) + << glyph->xMin << glyph->yMin << glyph->xMax << glyph->yMax; + + for (int i = 0; i < endPoints.size(); ++i) + s << quint16(endPoints.at(i)); + s << quint16(0); // instruction length + + // emit flags + for (int i = 0; i < points.size(); ++i) + s << quint8(points.at(i).flags); + // emit points + for (int i = 0; i < points.size(); ++i) { + quint8 flags = points.at(i).flags; + qint16 x = points.at(i).x; + + if (flags & XShortVector) + s << quint8(x); + else if (!(flags & XSame)) + s << qint16(x); + } + for (int i = 0; i < points.size(); ++i) { + quint8 flags = points.at(i).flags; + qint16 y = points.at(i).y; + + if (flags & YShortVector) + s << quint8(y); + else if (!(flags & YSame)) + s << qint16(y); + } + +// qDebug() << "offset=" << s.offset() << "max_size=" << max_size << "point_array_size=" << point_array_size; + Q_ASSERT(s.offset() == max_size); + + glyph->numContours = endPoints.size(); + glyph->numPoints = points.size(); +} + +static QTtfGlyph generateGlyph(int index, const QPainterPath &path, qreal advance, qreal lsb, qreal ppem) +{ + QList points; + QList endPoints; + QTtfGlyph glyph; + glyph.index = index; + glyph.advanceWidth = qRound(advance * 2048. / ppem); + glyph.lsb = qRound(lsb * 2048. / ppem); + + if (!path.elementCount()) { + //qDebug("glyph %d is empty", index); + lsb = 0; + glyph.xMin = glyph.xMax = glyph.yMin = glyph.yMax = 0; + glyph.numContours = 0; + glyph.numPoints = 0; + return glyph; + } + + convertPath(path, &points, &endPoints, ppem); + +// qDebug() << "number of contours=" << endPoints.size(); +// for (int i = 0; i < points.size(); ++i) +// qDebug() << " point[" << i << "] = " << QPoint(points.at(i).x, points.at(i).y) << " flags=" << points.at(i).flags; +// qDebug() << "endPoints:"; +// for (int i = 0; i < endPoints.size(); ++i) +// qDebug() << endPoints.at(i); + + getBounds(points, &glyph.xMin, &glyph.xMax, &glyph.yMin, &glyph.yMax); + int point_array_size = convertToRelative(&points); + getGlyphData(&glyph, points, endPoints, point_array_size); + return glyph; +} + +Q_STATIC_GLOBAL_OPERATOR bool operator <(const QTtfGlyph &g1, const QTtfGlyph &g2) +{ + return g1.index < g2.index; +} + +static QList generateGlyphTables(qttf_font_tables &tables, const QList &_glyphs) +{ + const int max_size_small = 65536*2; + QList glyphs = _glyphs; + qSort(glyphs); + + Q_ASSERT(tables.maxp.numGlyphs == glyphs.at(glyphs.size()-1).index + 1); + int nGlyphs = tables.maxp.numGlyphs; + + int glyf_size = 0; + for (int i = 0; i < glyphs.size(); ++i) + glyf_size += (glyphs.at(i).data.size() + 3) & ~3; + + tables.head.indexToLocFormat = glyf_size < max_size_small ? 0 : 1; + tables.hhea.numberOfHMetrics = nGlyphs; + + QTtfTable glyf; + glyf.tag = MAKE_TAG('g', 'l', 'y', 'f'); + + QTtfTable loca; + loca.tag = MAKE_TAG('l', 'o', 'c', 'a'); + loca.data.resize(glyf_size < max_size_small ? (nGlyphs+1)*sizeof(quint16) : (nGlyphs+1)*sizeof(quint32)); + QTtfStream ls(loca.data); + + QTtfTable hmtx; + hmtx.tag = MAKE_TAG('h', 'm', 't', 'x'); + hmtx.data.resize(nGlyphs*4); + QTtfStream hs(hmtx.data); + + int pos = 0; + for (int i = 0; i < nGlyphs; ++i) { + int gpos = glyf.data.size(); + quint16 advance = 0; + qint16 lsb = 0; + + if (glyphs[pos].index == i) { + // emit glyph +// qDebug("emitting glyph %d: size=%d", i, glyphs.at(i).data.size()); + glyf.data += glyphs.at(pos).data; + while (glyf.data.size() & 1) + glyf.data.append('\0'); + advance = glyphs.at(pos).advanceWidth; + lsb = glyphs.at(pos).lsb; + ++pos; + } + if (glyf_size < max_size_small) { + // use short loca format + ls << quint16(gpos>>1); + } else { + // use long loca format + ls << quint32(gpos); + } + hs << advance + << lsb; + } + if (glyf_size < max_size_small) { + // use short loca format + ls << quint16(glyf.data.size()>>1); + } else { + // use long loca format + ls << quint32(glyf.data.size()); + } + + Q_ASSERT(loca.data.size() == ls.offset()); + Q_ASSERT(hmtx.data.size() == hs.offset()); + + QList list; + list.append(glyf); + list.append(loca); + list.append(hmtx); + return list; +} + +Q_STATIC_GLOBAL_OPERATOR bool operator <(const QTtfTable &t1, const QTtfTable &t2) +{ + return t1.tag < t2.tag; +} + +static QByteArray bindFont(const QList& _tables) +{ + QList tables = _tables; + + qSort(tables); + + QByteArray font; + const int header_size = sizeof(qint32) + 4*sizeof(quint16); + const int directory_size = 4*sizeof(quint32)*tables.size(); + font.resize(header_size + directory_size); + + int log2 = 0; + int pow = 1; + int n = tables.size() >> 1; + while (n) { + ++log2; + pow <<= 1; + n >>= 1; + } + + quint32 head_offset = 0; + { + QTtfStream f(font); +// Offset Table +// Type Name Description +// qint32 sfnt version 0x00010000 for version 1.0. +// quint16 numTables Number of tables. +// quint16 searchRange (Maximum power of 2 <= numTables) x 16. +// quint16 entrySelector Log2(maximum power of 2 <= numTables). +// quint16 rangeShift NumTables x 16-searchRange. + f << qint32(0x00010000) + << quint16(tables.size()) + << quint16(16*pow) + << quint16(log2) + << quint16(16*(tables.size() - pow)); + +// Table Directory +// Type Name Description +// quint32 tag 4 -byte identifier. +// quint32 checkSum CheckSum for this table. +// quint32 offset Offset from beginning of TrueType font file. +// quint32 length Length of this table. + quint32 table_offset = header_size + directory_size; + for (int i = 0; i < tables.size(); ++i) { + const QTtfTable &t = tables.at(i); + const quint32 size = (t.data.size() + 3) & ~3; + if (t.tag == MAKE_TAG('h', 'e', 'a', 'd')) + head_offset = table_offset; + f << t.tag + << checksum(t.data) + << table_offset + << t.data.size(); + table_offset += size; +#define TAG(x) char(t.tag >> 24) << char((t.tag >> 16) & 0xff) << char((t.tag >> 8) & 0xff) << char(t.tag & 0xff) + //qDebug() << "table " << TAG(t.tag) << "has size " << t.data.size() << "stream at " << f.offset(); + } + } + for (int i = 0; i < tables.size(); ++i) { + const QByteArray &t = tables.at(i).data; + font += t; + int s = t.size(); + while (s & 3) { font += '\0'; ++s; } + } + + if (!head_offset) { + qWarning("QFontSubset: Font misses 'head' table"); + return QByteArray(); + } + + // calculate the fonts checksum and qToBigEndian into 'head's checksum_adjust + quint32 checksum_adjust = 0xB1B0AFBA - checksum(font); + qToBigEndian(checksum_adjust, (uchar *)font.data() + head_offset + 8); + + return font; +} + + +/* + PDF requires the following tables: + + head, hhea, loca, maxp, cvt , prep, glyf, hmtx, fpgm + + This means we don't have to add a os/2, post or name table. cvt , prep and fpgm could be empty + if really required. +*/ + +QByteArray QFontSubset::toTruetype() const +{ + qttf_font_tables font; + memset(&font, 0, sizeof(qttf_font_tables)); + + qreal ppem = fontEngine->fontDef.pixelSize; +#define TO_TTF(x) qRound(x * 2048. / ppem) + QList glyphs; + + QFontEngine::Properties properties = fontEngine->properties(); + // initialize some stuff needed in createWidthArray + emSquare = 2048; + widths.resize(nGlyphs()); + + // head table + font.head.font_revision = 0x00010000; + font.head.flags = (1 << 2) | (1 << 4); + font.head.created = 0; // ### + font.head.modified = 0; // ### + font.head.xMin = SHRT_MAX; + font.head.xMax = SHRT_MIN; + font.head.yMin = SHRT_MAX; + font.head.yMax = SHRT_MIN; + font.head.macStyle = (fontEngine->fontDef.weight > QFont::Normal) ? 1 : 0; + font.head.macStyle |= (fontEngine->fontDef.styleHint != QFont::StyleNormal) ? 1 : 0; + + // hhea table + font.hhea.ascender = qRound(properties.ascent); + font.hhea.descender = -qRound(properties.descent); + font.hhea.lineGap = qRound(properties.leading); + font.hhea.maxAdvanceWidth = TO_TTF(fontEngine->maxCharWidth()); + font.hhea.minLeftSideBearing = TO_TTF(fontEngine->minLeftBearing()); + font.hhea.minRightSideBearing = TO_TTF(fontEngine->minRightBearing()); + font.hhea.xMaxExtent = SHRT_MIN; + + font.maxp.numGlyphs = 0; + font.maxp.maxPoints = 0; + font.maxp.maxContours = 0; + font.maxp.maxCompositePoints = 0; + font.maxp.maxCompositeContours = 0; + font.maxp.maxComponentElements = 0; + font.maxp.maxComponentDepth = 0; + font.maxp.numGlyphs = nGlyphs(); + + + + uint sumAdvances = 0; + for (int i = 0; i < nGlyphs(); ++i) { + glyph_t g = glyph_indices.at(i); + QPainterPath path; + glyph_metrics_t metric; + fontEngine->getUnscaledGlyph(g, &path, &metric); + if (noEmbed) { + path = QPainterPath(); + if (g == 0) + path.addRect(QRectF(0, 0, 1000, 1000)); + } + QTtfGlyph glyph = generateGlyph(i, path, metric.xoff.toReal(), metric.x.toReal(), properties.emSquare.toReal()); + + font.head.xMin = qMin(font.head.xMin, glyph.xMin); + font.head.xMax = qMax(font.head.xMax, glyph.xMax); + font.head.yMin = qMin(font.head.yMin, glyph.yMin); + font.head.yMax = qMax(font.head.yMax, glyph.yMax); + + font.hhea.xMaxExtent = qMax(font.hhea.xMaxExtent, (qint16)(glyph.lsb + glyph.xMax - glyph.xMin)); + + font.maxp.maxPoints = qMax(font.maxp.maxPoints, glyph.numPoints); + font.maxp.maxContours = qMax(font.maxp.maxContours, glyph.numContours); + + if (glyph.xMax > glyph.xMin) + sumAdvances += glyph.xMax - glyph.xMin; + +// qDebug("adding glyph %d size=%d", glyph.index, glyph.data.size()); + glyphs.append(glyph); + widths[i] = glyph.advanceWidth; + } + + + QList tables = generateGlyphTables(font, glyphs); + tables.append(generateHead(font.head)); + tables.append(generateHhea(font.hhea)); + tables.append(generateMaxp(font.maxp)); + // name + QTtfTable name_table; + name_table.tag = MAKE_TAG('n', 'a', 'm', 'e'); + if (!noEmbed) + name_table.data = fontEngine->getSfntTable(name_table.tag); + if (name_table.data.isEmpty()) { + qttf_name_table name; + if (noEmbed) + name.copyright = QLatin1String("Fake font"); + else + name.copyright = QLatin1String(properties.copyright); + name.family = fontEngine->fontDef.family; + name.subfamily = QLatin1String("Regular"); // ###### + name.postscript_name = QLatin1String(properties.postscriptName); + name_table = generateName(name); + } + tables.append(name_table); + + if (!noEmbed) { + QTtfTable os2; + os2.tag = MAKE_TAG('O', 'S', '/', '2'); + os2.data = fontEngine->getSfntTable(os2.tag); + if (!os2.data.isEmpty()) + tables.append(os2); + } + + return bindFont(tables); +} + +// ------------------ Type 1 generation --------------------------- + +// needs at least 6 bytes of space in tmp +static const char *encodeNumber(int num, char *tmp) +{ + const char *ret = tmp; + if(num >= -107 && num <= 107) { + QPdf::toHex((uchar)(num + 139), tmp); + tmp += 2; + } else if (num > 107 && num <= 1131) { + num -= 108; + QPdf::toHex((uchar)((num >> 8) + 247), tmp); + tmp += 2; + QPdf::toHex((uchar)(num & 0xff), tmp); + tmp += 2; + } else if(num < - 107 && num >= -1131) { + num += 108; + num = -num; + QPdf::toHex((uchar)((num >> 8) + 251), tmp); + tmp += 2; + QPdf::toHex((uchar)(num & 0xff), tmp); + tmp += 2; + } else { + *tmp++ = 'f'; + *tmp++ = 'f'; + QPdf::toHex((uchar)(num >> 24), tmp); + tmp += 2; + QPdf::toHex((uchar)(num >> 16), tmp); + tmp += 2; + QPdf::toHex((uchar)(num >> 8), tmp); + tmp += 2; + QPdf::toHex((uchar)(num >> 0), tmp); + tmp += 2; + } + *tmp = 0; +// qDebug("encodeNumber: %d -> '%s'", num, ret); + return ret; +} + +static QByteArray charString(const QPainterPath &path, qreal advance, qreal lsb, qreal ppem) +{ + // the charstring commands we need + const char *hsbw = "0D"; + const char *closepath = "09"; + const char *moveto[3] = { "16", "04", "15" }; + const char *lineto[3] = { "06", "07", "05" }; + const char *rcurveto = "08"; + const char *endchar = "0E"; + + enum { horizontal = 1, vertical = 2 }; + + char tmp[16]; + + qreal factor = 1000./ppem; + + int lsb_i = qRound(lsb*factor); + int advance_i = qRound(advance*factor); +// qDebug("--- charstring"); + + // first of all add lsb and width to the charstring using the hsbw command + QByteArray charstring; + charstring += encodeNumber(lsb_i, tmp); + charstring += encodeNumber(advance_i, tmp); + charstring += hsbw; + + // add the path + int xl = lsb_i; + int yl = 0; + bool openpath = false; + for (int i = 0; i < path.elementCount(); ++i) { + const QPainterPath::Element &elm = path.elementAt(i); + int x = qRound(elm.x*factor); + int y = -qRound(elm.y*factor); + int dx = x - xl; + int dy = y - yl; + if (elm.type == QPainterPath::MoveToElement && openpath) { +// qDebug("closepath %s", closepath); + charstring += closepath; + } + if (elm.type == QPainterPath::MoveToElement || + elm.type == QPainterPath::LineToElement) { + int type = -1; + if (dx || !dy) { + charstring += encodeNumber(dx, tmp); + type += horizontal; +// qDebug("horizontal"); + } + if (dy) { + charstring += encodeNumber(dy, tmp); + type += vertical; +// qDebug("vertical"); + } +// qDebug("moveto/lineto %s", (elm.type == QPainterPath::MoveToElement ? moveto[type] : lineto[type])); + charstring += (elm.type == QPainterPath::MoveToElement ? moveto[type] : lineto[type]); + openpath = true; + xl = x; + yl = y; + } else { + Q_ASSERT(elm.type == QPainterPath::CurveToElement); + const QPainterPath::Element &elm2 = path.elementAt(++i); + const QPainterPath::Element &elm3 = path.elementAt(++i); + int x2 = qRound(elm2.x*factor); + int y2 = -qRound(elm2.y*factor); + int x3 = qRound(elm3.x*factor); + int y3 = -qRound(elm3.y*factor); + charstring += encodeNumber(dx, tmp); + charstring += encodeNumber(dy, tmp); + charstring += encodeNumber(x2 - x, tmp); + charstring += encodeNumber(y2 - y, tmp); + charstring += encodeNumber(x3 - x2, tmp); + charstring += encodeNumber(y3 - y2, tmp); + charstring += rcurveto; + openpath = true; + xl = x3; + yl = y3; +// qDebug("rcurveto"); + } + } + if (openpath) + charstring += closepath; + charstring += endchar; + if (charstring.length() > 240) { + int pos = 240; + while (pos < charstring.length()) { + charstring.insert(pos, '\n'); + pos += 241; + } + } + return charstring; +} + +#ifndef QT_NO_FREETYPE +static const char *helvetica_styles[4] = { + "Helvetica", + "Helvetica-Bold", + "Helvetica-Oblique", + "Helvetica-BoldOblique" +}; +static const char *times_styles[4] = { + "Times-Regular", + "Times-Bold", + "Times-Italic", + "Times-BoldItalic" +}; +static const char *courier_styles[4] = { + "Courier", + "Courier-Bold", + "Courier-Oblique", + "Courier-BoldOblique" +}; +#endif + +QByteArray QFontSubset::toType1() const +{ + QFontEngine::Properties properties = fontEngine->properties(); + QVector reverseMap = getReverseMap(); + + QByteArray font; + QPdf::ByteStream s(&font); + + QByteArray id = QByteArray::number(object_id); + QByteArray psname = properties.postscriptName; + psname.replace(' ', ""); + + standard_font = false; + +#ifndef QT_NO_FREETYPE + FT_Face face = ft_face(fontEngine); + if (face && !FT_IS_SCALABLE(face)) { + int style = 0; + if (fontEngine->fontDef.style) + style += 2; + if (fontEngine->fontDef.weight >= QFont::Bold) + style++; + if (fontEngine->fontDef.family.contains(QLatin1String("Helvetica"))) { + psname = helvetica_styles[style]; + standard_font = true; + } else if (fontEngine->fontDef.family.contains(QLatin1String("Times"))) { + psname = times_styles[style]; + standard_font = true; + } else if (fontEngine->fontDef.family.contains(QLatin1String("Courier"))) { + psname = courier_styles[style]; + standard_font = true; + } + } +#endif + s << "/F" << id << "-Base\n"; + if (standard_font) { + s << '/' << psname << " findfont\n" + "0 dict copy dup /NumGlyphs 0 put dup /CMap 256 array put def\n"; + } else { + s << "<<\n"; + if(!psname.isEmpty()) + s << "/FontName /" << psname << '\n'; + s << "/FontInfo <fsType << ">>\n" + "/FontType 1\n" + "/PaintType 0\n" + "/FontMatrix [.001 0 0 .001 0 0]\n" + "/FontBBox { 0 0 0 0 }\n" + "/Private <<\n" + "/password 5839\n" + "/MinFeature {16 16}\n" + "/BlueValues []\n" + "/lenIV -1\n" + ">>\n" + "/CharStrings << >>\n" + "/NumGlyphs 0\n" + "/CMap 256 array\n" + ">> def\n"; + } + s << type1AddedGlyphs(); + downloaded_glyphs = glyph_indices.size(); + + return font; +} + +QByteArray QFontSubset::type1AddedGlyphs() const +{ + if (downloaded_glyphs == glyph_indices.size()) + return QByteArray(); + + QFontEngine::Properties properties = fontEngine->properties(); + QVector reverseMap = getReverseMap(); + QByteArray glyphs; + QPdf::ByteStream s(&glyphs); + + int nGlyphs = glyph_indices.size(); + QByteArray id = QByteArray::number(object_id); + + s << 'F' << id << "-Base [\n"; + for (int i = downloaded_glyphs; i < nGlyphs; ++i) { + glyph_t g = glyph_indices.at(i); + QPainterPath path; + glyph_metrics_t metric; + fontEngine->getUnscaledGlyph(g, &path, &metric); + QByteArray charstring = charString(path, metric.xoff.toReal(), metric.x.toReal(), + properties.emSquare.toReal()); + s << glyphName(i, reverseMap); + if (!standard_font) + s << "\n<" << charstring << ">\n"; + } + s << (standard_font ? "] T1AddMapping\n" : "] T1AddGlyphs\n"); + return glyphs; +} + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER diff --git a/src/gui/text/qfontsubset_p.h b/src/gui/text/qfontsubset_p.h new file mode 100644 index 0000000000..a09aced355 --- /dev/null +++ b/src/gui/text/qfontsubset_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFONTSUBSET_P_H +#define QFONTSUBSET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qfontengine_p.h" + +#ifndef QT_NO_PRINTER + +QT_BEGIN_NAMESPACE + +class QFontSubset +{ +public: + QFontSubset(QFontEngine *fe, int obj_id = 0) + : object_id(obj_id), noEmbed(false), fontEngine(fe), downloaded_glyphs(0), standard_font(false) + { fontEngine->ref.ref(); addGlyph(0); } + ~QFontSubset() { + if (!fontEngine->ref.deref()) + delete fontEngine; + } + + QByteArray toTruetype() const; + QByteArray toType1() const; + QByteArray type1AddedGlyphs() const; + QByteArray widthArray() const; + QByteArray createToUnicodeMap() const; + QVector getReverseMap() const; + QByteArray glyphName(unsigned int glyph, const QVector reverseMap) const; + + static QByteArray glyphName(unsigned short unicode, bool symbol); + + int addGlyph(int index); + const int object_id; + bool noEmbed; + QFontEngine *fontEngine; + QList glyph_indices; + mutable int downloaded_glyphs; + mutable bool standard_font; + int nGlyphs() const { return glyph_indices.size(); } + mutable QFixed emSquare; + mutable QVector widths; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER + +#endif // QFONTSUBSET_P_H diff --git a/src/gui/text/qfragmentmap.cpp b/src/gui/text/qfragmentmap.cpp new file mode 100644 index 0000000000..8ada5bbab7 --- /dev/null +++ b/src/gui/text/qfragmentmap.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "qfragmentmap_p.h" + + diff --git a/src/gui/text/qfragmentmap_p.h b/src/gui/text/qfragmentmap_p.h new file mode 100644 index 0000000000..4057142b34 --- /dev/null +++ b/src/gui/text/qfragmentmap_p.h @@ -0,0 +1,888 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFRAGMENTMAP_P_H +#define QFRAGMENTMAP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qglobal.h" +#include +#include + +QT_BEGIN_NAMESPACE + + +template +class QFragment +{ +public: + quint32 parent; + quint32 left; + quint32 right; + quint32 color; + quint32 size_left_array[N]; + quint32 size_array[N]; + enum {size_array_max = N }; +}; + +template +class QFragmentMapData +{ + enum Color { Red, Black }; +public: + QFragmentMapData(); + ~QFragmentMapData(); + + void init(); + + class Header + { + public: + quint32 root; // this relies on being at the same position as parent in the fragment struct + quint32 tag; + quint32 freelist; + quint32 node_count; + quint32 allocated; + }; + + + enum {fragmentSize = sizeof(Fragment) }; + + + int length(uint field = 0) const; + + + inline Fragment *fragment(uint index) { + return (fragments + index); + } + inline const Fragment *fragment(uint index) const { + return (fragments + index); + } + + + inline Fragment &F(uint index) { return fragments[index] ; } + inline const Fragment &F(uint index) const { return fragments[index] ; } + + inline bool isRoot(uint index) const { + return !fragment(index)->parent; + } + + inline uint position(uint node, uint field = 0) const { + Q_ASSERT(field < Fragment::size_array_max); + const Fragment *f = fragment(node); + uint offset = f->size_left_array[field]; + while (f->parent) { + uint p = f->parent; + f = fragment(p); + if (f->right == node) + offset += f->size_left_array[field] + f->size_array[field]; + node = p; + } + return offset; + } + inline uint sizeRight(uint node, uint field = 0) const { + Q_ASSERT(field < Fragment::size_array_max); + uint sr = 0; + const Fragment *f = fragment(node); + node = f->right; + while (node) { + f = fragment(node); + sr += f->size_left_array[field] + f->size_array[field]; + node = f->right; + } + return sr; + } + inline uint sizeLeft(uint node, uint field = 0) const { + Q_ASSERT(field < Fragment::size_array_max); + return fragment(node)->size_left_array[field]; + } + + + inline uint size(uint node, uint field = 0) const { + Q_ASSERT(field < Fragment::size_array_max); + return fragment(node)->size_array[field]; + } + + inline void setSize(uint node, int new_size, uint field = 0) { + Q_ASSERT(field < Fragment::size_array_max); + Fragment *f = fragment(node); + int diff = new_size - f->size_array[field]; + f->size_array[field] = new_size; + while (f->parent) { + uint p = f->parent; + f = fragment(p); + if (f->left == node) + f->size_left_array[field] += diff; + node = p; + } + } + + + uint findNode(int k, uint field = 0) const; + + uint insert_single(int key, uint length); + uint erase_single(uint f); + + uint minimum(uint n) const { + while (n && fragment(n)->left) + n = fragment(n)->left; + return n; + } + + uint maximum(uint n) const { + while (n && fragment(n)->right) + n = fragment(n)->right; + return n; + } + + uint next(uint n) const; + uint previous(uint n) const; + + inline uint root() const { + Q_ASSERT(!head->root || !fragment(head->root)->parent); + return head->root; + } + inline void setRoot(uint new_root) { + Q_ASSERT(!head->root || !fragment(new_root)->parent); + head->root = new_root; + } + + inline bool isValid(uint n) const { + return n > 0 && n != head->freelist; + } + + union { + Header *head; + Fragment *fragments; + }; + +private: + + void rotateLeft(uint x); + void rotateRight(uint x); + void rebalance(uint x); + void removeAndRebalance(uint z); + + uint createFragment(); + void freeFragment(uint f); + +}; + +template +QFragmentMapData::QFragmentMapData() + : fragments(0) +{ + init(); +} + +template +void QFragmentMapData::init() +{ + // the following code will realloc an existing fragment or create a new one. + // it will also ignore errors when shrinking an existing fragment. + Fragment *newFragments = (Fragment *)realloc(fragments, 64*fragmentSize); + if (newFragments) { + fragments = newFragments; + head->allocated = 64; + } + Q_CHECK_PTR(fragments); + + head->tag = (((quint32)'p') << 24) | (((quint32)'m') << 16) | (((quint32)'a') << 8) | 'p'; //TAG('p', 'm', 'a', 'p'); + head->root = 0; + head->freelist = 1; + head->node_count = 0; + // mark all items to the right as unused + F(head->freelist).right = 0; +} + +template +QFragmentMapData::~QFragmentMapData() +{ + free(fragments); +} + +template +uint QFragmentMapData::createFragment() +{ + Q_ASSERT(head->freelist <= head->allocated); + + uint freePos = head->freelist; + if (freePos == head->allocated) { + // need to create some free space + uint needed = qAllocMore((freePos+1)*fragmentSize, 0); + Q_ASSERT(needed/fragmentSize > head->allocated); + Fragment *newFragments = (Fragment *)realloc(fragments, needed); + Q_CHECK_PTR(newFragments); + fragments = newFragments; + head->allocated = needed/fragmentSize; + F(freePos).right = 0; + } + + uint nextPos = F(freePos).right; + if (!nextPos) { + nextPos = freePos+1; + if (nextPos < head->allocated) + F(nextPos).right = 0; + } + + head->freelist = nextPos; + + ++head->node_count; + + return freePos; +} + +template +void QFragmentMapData::freeFragment(uint i) +{ + F(i).right = head->freelist; + head->freelist = i; + + --head->node_count; +} + + +template +uint QFragmentMapData::next(uint n) const { + Q_ASSERT(n); + if (F(n).right) { + n = F(n).right; + while (F(n).left) + n = F(n).left; + } else { + uint y = F(n).parent; + while (F(n).parent && n == F(y).right) { + n = y; + y = F(y).parent; + } + n = y; + } + return n; +} + +template +uint QFragmentMapData::previous(uint n) const { + if (!n) + return maximum(root()); + + if (F(n).left) { + n = F(n).left; + while (F(n).right) + n = F(n).right; + } else { + uint y = F(n).parent; + while (F(n).parent && n == F(y).left) { + n = y; + y = F(y).parent; + } + n = y; + } + return n; +} + + +/* + x y + \ / \ + y --> x b + / \ \ + a b a +*/ +template +void QFragmentMapData::rotateLeft(uint x) +{ + uint p = F(x).parent; + uint y = F(x).right; + + + if (y) { + F(x).right = F(y).left; + if (F(y).left) + F(F(y).left).parent = x; + F(y).left = x; + F(y).parent = p; + } else { + F(x).right = 0; + } + if (!p) { + Q_ASSERT(head->root == x); + head->root = y; + } + else if (x == F(p).left) + F(p).left = y; + else + F(p).right = y; + F(x).parent = y; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(y).size_left_array[field] += F(x).size_left_array[field] + F(x).size_array[field]; +} + + +/* + x y + / / \ + y --> a x + / \ / + a b b +*/ +template +void QFragmentMapData::rotateRight(uint x) +{ + uint y = F(x).left; + uint p = F(x).parent; + + if (y) { + F(x).left = F(y).right; + if (F(y).right) + F(F(y).right).parent = x; + F(y).right = x; + F(y).parent = p; + } else { + F(x).left = 0; + } + if (!p) { + Q_ASSERT(head->root == x); + head->root = y; + } + else if (x == F(p).right) + F(p).right = y; + else + F(p).left = y; + F(x).parent = y; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(x).size_left_array[field] -= F(y).size_left_array[field] + F(y).size_array[field]; +} + + +template +void QFragmentMapData::rebalance(uint x) +{ + F(x).color = Red; + + while (F(x).parent && F(F(x).parent).color == Red) { + uint p = F(x).parent; + uint pp = F(p).parent; + Q_ASSERT(pp); + if (p == F(pp).left) { + uint y = F(pp).right; + if (y && F(y).color == Red) { + F(p).color = Black; + F(y).color = Black; + F(pp).color = Red; + x = pp; + } else { + if (x == F(p).right) { + x = p; + rotateLeft(x); + p = F(x).parent; + pp = F(p).parent; + } + F(p).color = Black; + if (pp) { + F(pp).color = Red; + rotateRight(pp); + } + } + } else { + uint y = F(pp).left; + if (y && F(y).color == Red) { + F(p).color = Black; + F(y).color = Black; + F(pp).color = Red; + x = pp; + } else { + if (x == F(p).left) { + x = p; + rotateRight(x); + p = F(x).parent; + pp = F(p).parent; + } + F(p).color = Black; + if (pp) { + F(pp).color = Red; + rotateLeft(pp); + } + } + } + } + F(root()).color = Black; +} + + +template +uint QFragmentMapData::erase_single(uint z) +{ + uint w = previous(z); + uint y = z; + uint x; + uint p; + + if (!F(y).left) { + x = F(y).right; + } else if (!F(y).right) { + x = F(y).left; + } else { + y = F(y).right; + while (F(y).left) + y = F(y).left; + x = F(y).right; + } + + if (y != z) { + F(F(z).left).parent = y; + F(y).left = F(z).left; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(y).size_left_array[field] = F(z).size_left_array[field]; + if (y != F(z).right) { + /* + z y + / \ / \ + a b a b + / / + ... --> ... + / / + y x + / \ + 0 x + */ + p = F(y).parent; + if (x) + F(x).parent = p; + F(p).left = x; + F(y).right = F(z).right; + F(F(z).right).parent = y; + uint n = p; + while (n != y) { + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(n).size_left_array[field] -= F(y).size_array[field]; + n = F(n).parent; + } + } else { + /* + z y + / \ / \ + a y --> a x + / \ + 0 x + */ + p = y; + } + uint zp = F(z).parent; + if (!zp) { + Q_ASSERT(head->root == z); + head->root = y; + } else if (F(zp).left == z) { + F(zp).left = y; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(zp).size_left_array[field] -= F(z).size_array[field]; + } else { + F(zp).right = y; + } + F(y).parent = zp; + // Swap the colors + uint c = F(y).color; + F(y).color = F(z).color; + F(z).color = c; + y = z; + } else { + /* + p p p p + / / \ \ + z --> x z --> x + | | + x x + */ + p = F(z).parent; + if (x) + F(x).parent = p; + if (!p) { + Q_ASSERT(head->root == z); + head->root = x; + } else if (F(p).left == z) { + F(p).left = x; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(p).size_left_array[field] -= F(z).size_array[field]; + } else { + F(p).right = x; + } + } + uint n = z; + while (F(n).parent) { + uint p = F(n).parent; + if (F(p).left == n) { + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(p).size_left_array[field] -= F(z).size_array[field]; + } + n = p; + } + + freeFragment(z); + + + if (F(y).color != Red) { + while (F(x).parent && (x == 0 || F(x).color == Black)) { + if (x == F(p).left) { + uint w = F(p).right; + if (F(w).color == Red) { + F(w).color = Black; + F(p).color = Red; + rotateLeft(p); + w = F(p).right; + } + if ((F(w).left == 0 || F(F(w).left).color == Black) && + (F(w).right == 0 || F(F(w).right).color == Black)) { + F(w).color = Red; + x = p; + p = F(x).parent; + } else { + if (F(w).right == 0 || F(F(w).right).color == Black) { + if (F(w).left) + F(F(w).left).color = Black; + F(w).color = Red; + rotateRight(F(p).right); + w = F(p).right; + } + F(w).color = F(p).color; + F(p).color = Black; + if (F(w).right) + F(F(w).right).color = Black; + rotateLeft(p); + break; + } + } else { + uint w = F(p).left; + if (F(w).color == Red) { + F(w).color = Black; + F(p).color = Red; + rotateRight(p); + w = F(p).left; + } + if ((F(w).right == 0 || F(F(w).right).color == Black) && + (F(w).left == 0 || F(F(w).left).color == Black)) { + F(w).color = Red; + x = p; + p = F(x).parent; + } else { + if (F(w).left == 0 || F(F(w).left).color == Black) { + if (F(w).right) + F(F(w).right).color = Black; + F(w).color = Red; + rotateLeft(F(p).left); + w = F(p).left; + } + F(w).color = F(p).color; + F(p).color = Black; + if (F(w).left) + F(F(w).left).color = Black; + rotateRight(p); + break; + } + } + } + if (x) + F(x).color = Black; + } + + return w; +} + +template +uint QFragmentMapData::findNode(int k, uint field) const +{ + Q_ASSERT(field < Fragment::size_array_max); + uint x = root(); + + uint s = k; + while (x) { + if (sizeLeft(x, field) <= s) { + if (s < sizeLeft(x, field) + size(x, field)) + return x; + s -= sizeLeft(x, field) + size(x, field); + x = F(x).right; + } else { + x = F(x).left; + } + } + return 0; +} + +template +uint QFragmentMapData::insert_single(int key, uint length) +{ + Q_ASSERT(!findNode(key) || (int)this->position(findNode(key)) == key); + + uint z = createFragment(); + + F(z).left = 0; + F(z).right = 0; + F(z).size_array[0] = length; + for (uint field = 1; field < Fragment::size_array_max; ++field) + F(z).size_array[field] = 1; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(z).size_left_array[field] = 0; + + uint y = 0; + uint x = root(); + + Q_ASSERT(!x || F(x).parent == 0); + + uint s = key; + bool right = false; + while (x) { + y = x; + if (s <= F(x).size_left_array[0]) { + x = F(x).left; + right = false; + } else { + s -= F(x).size_left_array[0] + F(x).size_array[0]; + x = F(x).right; + right = true; + } + } + + F(z).parent = y; + if (!y) { + head->root = z; + } else if (!right) { + F(y).left = z; + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(y).size_left_array[field] = F(z).size_array[field]; + } else { + F(y).right = z; + } + while (y && F(y).parent) { + uint p = F(y).parent; + if (F(p).left == y) { + for (uint field = 0; field < Fragment::size_array_max; ++field) + F(p).size_left_array[field] += F(z).size_array[field]; + } + y = p; + } + rebalance(z); + + return z; +} + + +template +int QFragmentMapData::length(uint field) const { + uint root = this->root(); + return root ? sizeLeft(root, field) + size(root, field) + sizeRight(root, field) : 0; +} + + +template // NOTE: must inherit QFragment +class QFragmentMap +{ +public: + class Iterator + { + public: + QFragmentMap *pt; + quint32 n; + + Iterator() : pt(0), n(0) {} + Iterator(QFragmentMap *p, int node) : pt(p), n(node) {} + Iterator(const Iterator& it) : pt(it.pt), n(it.n) {} + + inline bool atEnd() const { return !n; } + + bool operator==(const Iterator& it) const { return pt == it.pt && n == it.n; } + bool operator!=(const Iterator& it) const { return pt != it.pt || n != it.n; } + bool operator<(const Iterator &it) const { return position() < it.position(); } + + Fragment *operator*() { Q_ASSERT(!atEnd()); return pt->fragment(n); } + const Fragment *operator*() const { Q_ASSERT(!atEnd()); return pt->fragment(n); } + Fragment *operator->() { Q_ASSERT(!atEnd()); return pt->fragment(n); } + const Fragment *operator->() const { Q_ASSERT(!atEnd()); return pt->fragment(n); } + + int position() const { Q_ASSERT(!atEnd()); return pt->data.position(n); } + const Fragment *value() const { Q_ASSERT(!atEnd()); return pt->fragment(n); } + Fragment *value() { Q_ASSERT(!atEnd()); return pt->fragment(n); } + + Iterator& operator++() { + n = pt->data.next(n); + return *this; + } + Iterator& operator--() { + n = pt->data.previous(n); + return *this; + } + + }; + + + class ConstIterator + { + public: + const QFragmentMap *pt; + quint32 n; + + /** + * Functions + */ + ConstIterator() : pt(0), n(0) {} + ConstIterator(const QFragmentMap *p, int node) : pt(p), n(node) {} + ConstIterator(const ConstIterator& it) : pt(it.pt), n(it.n) {} + ConstIterator(const Iterator& it) : pt(it.pt), n(it.n) {} + + inline bool atEnd() const { return !n; } + + bool operator==(const ConstIterator& it) const { return pt == it.pt && n == it.n; } + bool operator!=(const ConstIterator& it) const { return pt != it.pt || n != it.n; } + bool operator<(const ConstIterator &it) const { return position() < it.position(); } + + const Fragment *operator*() const { Q_ASSERT(!atEnd()); return pt->fragment(n); } + const Fragment *operator->() const { Q_ASSERT(!atEnd()); return pt->fragment(n); } + + int position() const { Q_ASSERT(!atEnd()); return pt->data.position(n); } + int size() const { Q_ASSERT(!atEnd()); return pt->data.size(n); } + const Fragment *value() const { Q_ASSERT(!atEnd()); return pt->fragment(n); } + + ConstIterator& operator++() { + n = pt->data.next(n); + return *this; + } + ConstIterator& operator--() { + n = pt->data.previous(n); + return *this; + } + }; + + + QFragmentMap() {} + ~QFragmentMap() + { + if (!data.fragments) + return; // in case of out-of-memory, we won't have fragments + for (Iterator it = begin(); !it.atEnd(); ++it) + it.value()->free(); + } + + inline void clear() { + for (Iterator it = begin(); !it.atEnd(); ++it) + it.value()->free(); + data.init(); + } + + inline Iterator begin() { return Iterator(this, data.minimum(data.root())); } + inline Iterator end() { return Iterator(this, 0); } + inline ConstIterator begin() const { return ConstIterator(this, data.minimum(data.root())); } + inline ConstIterator end() const { return ConstIterator(this, 0); } + + inline ConstIterator last() const { return ConstIterator(this, data.maximum(data.root())); } + + inline bool isEmpty() const { return data.head->node_count == 0; } + inline int numNodes() const { return data.head->node_count; } + int length(uint field = 0) const { return data.length(field); } + + Iterator find(int k, uint field = 0) { return Iterator(this, data.findNode(k, field)); } + ConstIterator find(int k, uint field = 0) const { return ConstIterator(this, data.findNode(k, field)); } + + uint findNode(int k, uint field = 0) const { return data.findNode(k, field); } + + uint insert_single(int key, uint length) + { + uint f = data.insert_single(key, length); + if (f != 0) { + Fragment *frag = fragment(f); + Q_ASSERT(frag); + frag->initialize(); + } + return f; + } + uint erase_single(uint f) + { + if (f != 0) { + Fragment *frag = fragment(f); + Q_ASSERT(frag); + frag->free(); + } + return data.erase_single(f); + } + + inline Fragment *fragment(uint index) { + Q_ASSERT(index != 0); + return data.fragment(index); + } + inline const Fragment *fragment(uint index) const { + Q_ASSERT(index != 0); + return data.fragment(index); + } + inline uint position(uint node, uint field = 0) const { return data.position(node, field); } + inline bool isValid(uint n) const { return data.isValid(n); } + inline uint next(uint n) const { return data.next(n); } + inline uint previous(uint n) const { return data.previous(n); } + inline uint size(uint node, uint field = 0) const { return data.size(node, field); } + inline void setSize(uint node, int new_size, uint field = 0) + { data.setSize(node, new_size, field); + if (node != 0 && field == 0) { + Fragment *frag = fragment(node); + Q_ASSERT(frag); + frag->invalidate(); + } + } + + inline int firstNode() const { return data.minimum(data.root()); } + +private: + friend class Iterator; + friend class ConstIterator; + + QFragmentMapData data; + + QFragmentMap(const QFragmentMap& m); + QFragmentMap& operator= (const QFragmentMap& m); +}; + +QT_END_NAMESPACE + +#endif // QFRAGMENTMAP_P_H diff --git a/src/gui/text/qglyphs.cpp b/src/gui/text/qglyphs.cpp new file mode 100644 index 0000000000..b8a418de44 --- /dev/null +++ b/src/gui/text/qglyphs.cpp @@ -0,0 +1,323 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" + +#if !defined(QT_NO_RAWFONT) + +#include "qglyphs.h" +#include "qglyphs_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGlyphs + \brief the QGlyphs class provides direct access to the internal glyphs in a font + \since 4.8 + + \ingroup text + \mainclass + + When Qt displays a string of text encoded in Unicode, it will first convert the Unicode points + into a list of glyph indexes and a list of positions based on one or more fonts. The Unicode + representation of the text and the QFont object will in this case serve as a convenient + abstraction that hides the details of what actually takes place when displaying the text + on-screen. For instance, by the time the text actually reaches the screen, it may be represented + by a set of fonts in addition to the one specified by the user, e.g. in case the originally + selected font did not support all the writing systems contained in the text. + + Under certain circumstances, it can be useful as an application developer to have more low-level + control over which glyphs in a specific font are drawn to the screen. This could for instance + be the case in applications that use an external font engine and text shaper together with Qt. + QGlyphs provides an interface to the raw data needed to get text on the screen. It + contains a list of glyph indexes, a position for each glyph and a font. + + It is the user's responsibility to ensure that the selected font actually contains the + provided glyph indexes. + + QTextLayout::glyphs() or QTextFragment::glyphs() can be used to convert unicode encoded text + into a list of QGlyphs objects, and QPainter::drawGlyphs() can be used to draw the glyphs. + + \note Please note that QRawFont is considered local to the thread in which it is constructed, + which in turn means that a new QRawFont will have to be created and set on the QGlyphs if it is + moved to a different thread. If the QGlyphs contains a reference to a QRawFont from a different + thread than the current, it will not be possible to draw the glyphs using a QPainter, as the + QRawFont is considered invalid and inaccessible in this case. +*/ + + +/*! + Constructs an empty QGlyphs object. +*/ +QGlyphs::QGlyphs() : d(new QGlyphsPrivate) +{ +} + +/*! + Constructs a QGlyphs object which is a copy of \a other. +*/ +QGlyphs::QGlyphs(const QGlyphs &other) +{ + d = other.d; +} + +/*! + Destroys the QGlyphs. +*/ +QGlyphs::~QGlyphs() +{ + // Required for QExplicitlySharedDataPointer +} + +/*! + \internal +*/ +void QGlyphs::detach() +{ + if (d->ref != 1) + d.detach(); +} + +/*! + Assigns \a other to this QGlyphs object. +*/ +QGlyphs &QGlyphs::operator=(const QGlyphs &other) +{ + d = other.d; + return *this; +} + +/*! + Compares \a other to this QGlyphs object. Returns true if the list of glyph indexes, + the list of positions and the font are all equal, otherwise returns false. +*/ +bool QGlyphs::operator==(const QGlyphs &other) const +{ + return ((d == other.d) + || (d->glyphIndexes == other.d->glyphIndexes + && d->glyphPositions == other.d->glyphPositions + && d->overline == other.d->overline + && d->underline == other.d->underline + && d->strikeOut == other.d->strikeOut + && d->font == other.d->font)); +} + +/*! + Compares \a other to this QGlyphs object. Returns true if any of the list of glyph + indexes, the list of positions or the font are different, otherwise returns false. +*/ +bool QGlyphs::operator!=(const QGlyphs &other) const +{ + return !(*this == other); +} + +/*! + \internal + + Adds together the lists of glyph indexes and positions in \a other and this QGlyphs + object and returns the result. The font in the returned QGlyphs will be the same as in + this QGlyphs object. +*/ +QGlyphs QGlyphs::operator+(const QGlyphs &other) const +{ + QGlyphs ret(*this); + ret += other; + return ret; +} + +/*! + \internal + + Appends the glyph indexes and positions in \a other to this QGlyphs object and returns + a reference to the current object. +*/ +QGlyphs &QGlyphs::operator+=(const QGlyphs &other) +{ + detach(); + + d->glyphIndexes += other.d->glyphIndexes; + d->glyphPositions += other.d->glyphPositions; + + return *this; +} + +/*! + Returns the font selected for this QGlyphs object. + + \sa setFont() +*/ +QRawFont QGlyphs::font() const +{ + return d->font; +} + +/*! + Sets the font in which to look up the glyph indexes to \a font. + + \sa font(), setGlyphIndexes() +*/ +void QGlyphs::setFont(const QRawFont &font) +{ + detach(); + d->font = font; +} + +/*! + Returns the glyph indexes for this QGlyphs object. + + \sa setGlyphIndexes(), setPositions() +*/ +QVector QGlyphs::glyphIndexes() const +{ + return d->glyphIndexes; +} + +/*! + Set the glyph indexes for this QGlyphs object to \a glyphIndexes. The glyph indexes must + be valid for the selected font. +*/ +void QGlyphs::setGlyphIndexes(const QVector &glyphIndexes) +{ + detach(); + d->glyphIndexes = glyphIndexes; +} + +/*! + Returns the position of the edge of the baseline for each glyph in this set of glyph indexes. +*/ +QVector QGlyphs::positions() const +{ + return d->glyphPositions; +} + +/*! + Sets the positions of the edge of the baseline for each glyph in this set of glyph indexes to + \a positions. +*/ +void QGlyphs::setPositions(const QVector &positions) +{ + detach(); + d->glyphPositions = positions; +} + +/*! + Clears all data in the QGlyphs object. +*/ +void QGlyphs::clear() +{ + detach(); + d->glyphPositions = QVector(); + d->glyphIndexes = QVector(); + d->font = QRawFont(); + d->strikeOut = false; + d->overline = false; + d->underline = false; +} + +/*! + Returns true if this QGlyphs should be painted with an overline decoration. + + \sa setOverline() +*/ +bool QGlyphs::overline() const +{ + return d->overline; +} + +/*! + Indicates that this QGlyphs should be painted with an overline decoration if \a overline is true. + Otherwise the QGlyphs should be painted with no overline decoration. + + \sa overline() +*/ +void QGlyphs::setOverline(bool overline) +{ + detach(); + d->overline = overline; +} + +/*! + Returns true if this QGlyphs should be painted with an underline decoration. + + \sa setUnderline() +*/ +bool QGlyphs::underline() const +{ + return d->underline; +} + +/*! + Indicates that this QGlyphs should be painted with an underline decoration if \a underline is + true. Otherwise the QGlyphs should be painted with no underline decoration. + + \sa underline() +*/ +void QGlyphs::setUnderline(bool underline) +{ + detach(); + d->underline = underline; +} + +/*! + Returns true if this QGlyphs should be painted with a strike out decoration. + + \sa setStrikeOut() +*/ +bool QGlyphs::strikeOut() const +{ + return d->strikeOut; +} + +/*! + Indicates that this QGlyphs should be painted with an strike out decoration if \a strikeOut is + true. Otherwise the QGlyphs should be painted with no strike out decoration. + + \sa strikeOut() +*/ +void QGlyphs::setStrikeOut(bool strikeOut) +{ + detach(); + d->strikeOut = strikeOut; +} + +QT_END_NAMESPACE + +#endif // QT_NO_RAWFONT diff --git a/src/gui/text/qglyphs.h b/src/gui/text/qglyphs.h new file mode 100644 index 0000000000..4d7dcaf554 --- /dev/null +++ b/src/gui/text/qglyphs.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLYPHS_H +#define QGLYPHS_H + +#include +#include +#include +#include + +#if !defined(QT_NO_RAWFONT) + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QGlyphsPrivate; +class Q_GUI_EXPORT QGlyphs +{ +public: + QGlyphs(); + QGlyphs(const QGlyphs &other); + ~QGlyphs(); + + QRawFont font() const; + void setFont(const QRawFont &font); + + QVector glyphIndexes() const; + void setGlyphIndexes(const QVector &glyphIndexes); + + QVector positions() const; + void setPositions(const QVector &positions); + + void clear(); + + QGlyphs &operator=(const QGlyphs &other); + bool operator==(const QGlyphs &other) const; + bool operator!=(const QGlyphs &other) const; + + void setOverline(bool overline); + bool overline() const; + + void setUnderline(bool underline); + bool underline() const; + + void setStrikeOut(bool strikeOut); + bool strikeOut() const; + +private: + friend class QGlyphsPrivate; + friend class QTextLine; + + QGlyphs operator+(const QGlyphs &other) const; + QGlyphs &operator+=(const QGlyphs &other); + + void detach(); + QExplicitlySharedDataPointer d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_RAWFONT + +#endif // QGLYPHS_H diff --git a/src/gui/text/qglyphs_p.h b/src/gui/text/qglyphs_p.h new file mode 100644 index 0000000000..944f777d4a --- /dev/null +++ b/src/gui/text/qglyphs_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLYPHS_P_H +#define QGLYPHS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of internal files. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include "qglyphs.h" +#include "qrawfont.h" + +#include + +#if !defined(QT_NO_RAWFONT) + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QGlyphsPrivate: public QSharedData +{ +public: + QGlyphsPrivate() + : overline(false) + , underline(false) + , strikeOut(false) + { + } + + QGlyphsPrivate(const QGlyphsPrivate &other) + : QSharedData(other) + , glyphIndexes(other.glyphIndexes) + , glyphPositions(other.glyphPositions) + , font(other.font) + , overline(other.overline) + , underline(other.underline) + , strikeOut(other.strikeOut) + { + } + + QVector glyphIndexes; + QVector glyphPositions; + QRawFont font; + + uint overline : 1; + uint underline : 1; + uint strikeOut : 1; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGLYPHS_P_H + +#endif // QT_NO_RAWFONT diff --git a/src/gui/text/qpfutil.cpp b/src/gui/text/qpfutil.cpp new file mode 100644 index 0000000000..734d4c3592 --- /dev/null +++ b/src/gui/text/qpfutil.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +static const QFontEngineQPF::TagType tagTypes[QFontEngineQPF::NumTags] = { + QFontEngineQPF::StringType, // FontName + QFontEngineQPF::StringType, // FileName + QFontEngineQPF::UInt32Type, // FileIndex + QFontEngineQPF::UInt32Type, // FontRevision + QFontEngineQPF::StringType, // FreeText + QFontEngineQPF::FixedType, // Ascent + QFontEngineQPF::FixedType, // Descent + QFontEngineQPF::FixedType, // Leading + QFontEngineQPF::FixedType, // XHeight + QFontEngineQPF::FixedType, // AverageCharWidth + QFontEngineQPF::FixedType, // MaxCharWidth + QFontEngineQPF::FixedType, // LineThickness + QFontEngineQPF::FixedType, // MinLeftBearing + QFontEngineQPF::FixedType, // MinRightBearing + QFontEngineQPF::FixedType, // UnderlinePosition + QFontEngineQPF::UInt8Type, // GlyphFormat + QFontEngineQPF::UInt8Type, // PixelSize + QFontEngineQPF::UInt8Type, // Weight + QFontEngineQPF::UInt8Type, // Style + QFontEngineQPF::StringType, // EndOfHeader + QFontEngineQPF::BitFieldType// WritingSystems +}; + + diff --git a/src/gui/text/qplatformfontdatabase_qpa.cpp b/src/gui/text/qplatformfontdatabase_qpa.cpp new file mode 100644 index 0000000000..6fa25e7ea2 --- /dev/null +++ b/src/gui/text/qplatformfontdatabase_qpa.cpp @@ -0,0 +1,291 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qplatformfontdatabase_qpa.h" +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +extern void qt_registerFont(const QString &familyname, const QString &foundryname, int weight, + QFont::Style style, int stretch, bool antialiased,bool scalable, int pixelSize, + const QSupportedWritingSystems &writingSystems, void *hanlde); + +void QPlatformFontDatabase::registerQPF2Font(const QByteArray &dataArray, void *handle) +{ + if (dataArray.size() == 0) + return; + + const uchar *data = reinterpret_cast(dataArray.constData()); + if (QFontEngineQPA::verifyHeader(data, dataArray.size())) { + QString fontName = QFontEngineQPA::extractHeaderField(data, QFontEngineQPA::Tag_FontName).toString(); + int pixelSize = QFontEngineQPA::extractHeaderField(data, QFontEngineQPA::Tag_PixelSize).toInt(); + QVariant weight = QFontEngineQPA::extractHeaderField(data, QFontEngineQPA::Tag_Weight); + QVariant style = QFontEngineQPA::extractHeaderField(data, QFontEngineQPA::Tag_Style); + QByteArray writingSystemBits = QFontEngineQPA::extractHeaderField(data, QFontEngineQPA::Tag_WritingSystems).toByteArray(); + + if (!fontName.isEmpty() && pixelSize) { + QFont::Weight fontWeight = QFont::Normal; + if (weight.type() == QVariant::Int || weight.type() == QVariant::UInt) + fontWeight = QFont::Weight(weight.toInt()); + + QFont::Style fontStyle = static_cast(style.toInt()); + + QSupportedWritingSystems writingSystems; + for (int i = 0; i < writingSystemBits.count(); ++i) { + uchar currentByte = writingSystemBits.at(i); + for (int j = 0; j < 8; ++j) { + if (currentByte & 1) + writingSystems.setSupported(QFontDatabase::WritingSystem(i * 8 + j)); + currentByte >>= 1; + } + } + QFont::Stretch stretch = QFont::Unstretched; + registerFont(fontName,QString(),fontWeight,fontStyle,stretch,true,false,pixelSize,writingSystems,handle); + } + } else { + qDebug() << "header verification of QPF2 font failed. maybe it is corrupt?"; + } +} + +void QPlatformFontDatabase::registerFont(const QString &familyname, const QString &foundryname, QFont::Weight weight, + QFont::Style style, QFont::Stretch stretch, bool antialiased, bool scalable, int pixelSize, + const QSupportedWritingSystems &writingSystems, void *usrPtr) +{ + if (scalable) + pixelSize = 0; + qt_registerFont(familyname,foundryname,weight,style,stretch,antialiased,scalable,pixelSize,writingSystems,usrPtr); +} + +class QWritingSystemsPrivate +{ +public: + QWritingSystemsPrivate() + : ref(1) + , vector(QFontDatabase::WritingSystemsCount,false) + { + } + + QWritingSystemsPrivate(const QWritingSystemsPrivate *other) + : ref(1) + , vector(other->vector) + { + } + + QAtomicInt ref; + QVector vector; +}; + +QSupportedWritingSystems::QSupportedWritingSystems() +{ + d = new QWritingSystemsPrivate; +} + +QSupportedWritingSystems::QSupportedWritingSystems(const QSupportedWritingSystems &other) +{ + d = other.d; + d->ref.ref(); +} + +QSupportedWritingSystems &QSupportedWritingSystems::operator=(const QSupportedWritingSystems &other) +{ + if (d != other.d) { + other.d->ref.ref(); + if (!d->ref.deref()) + delete d; + d = other.d; + } + return *this; +} + +QSupportedWritingSystems::~QSupportedWritingSystems() +{ + if (!d->ref.deref()) + delete d; +} + +void QSupportedWritingSystems::detach() +{ + if (d->ref != 1) { + QWritingSystemsPrivate *newd = new QWritingSystemsPrivate(d); + if (!d->ref.deref()) + delete d; + d = newd; + } +} + +void QSupportedWritingSystems::setSupported(QFontDatabase::WritingSystem writingSystem, bool support) +{ + detach(); + d->vector[writingSystem] = support; +} + +bool QSupportedWritingSystems::supported(QFontDatabase::WritingSystem writingSystem) const +{ + return d->vector.at(writingSystem); +} + +/*! + \class QSupportedWritingSystems + \brief The QSupportedWritingSystems class is used when registering fonts with the internal Qt + fontdatabase + \ingroup painting + + Its to provide an easy to use interface for indicating what writing systems a specific font + supports. + +*/ + +/*! + This function is called once at startup by Qts internal fontdatabase. Reimplement this function + in a subclass for a convenient place to initialise the internal fontdatabase. + + The default implementation looks in the fontDir() location and registers all qpf2 fonts. +*/ +void QPlatformFontDatabase::populateFontDatabase() +{ + QString fontpath = fontDir(); + + if(!QFile::exists(fontpath)) { + qFatal("QFontDatabase: Cannot find font directory %s - is Qt installed correctly?", + qPrintable(fontpath)); + } + + QDir dir(fontpath); + dir.setNameFilters(QStringList() << QLatin1String("*.qpf2")); + dir.refresh(); + for (int i = 0; i < int(dir.count()); ++i) { + const QByteArray fileName = QFile::encodeName(dir.absoluteFilePath(dir[i])); + QFile file(QString::fromLocal8Bit(fileName)); + if (file.open(QFile::ReadOnly)) { + const QByteArray fileData = file.readAll(); + QByteArray *fileDataPtr = new QByteArray(fileData); + registerQPF2Font(fileData, fileDataPtr); + } + } +} + +/*! + +*/ +QFontEngine *QPlatformFontDatabase::fontEngine(const QFontDef &fontDef, QUnicodeTables::Script script, void *handle) +{ + Q_UNUSED(script); + Q_UNUSED(handle); + QByteArray *fileDataPtr = static_cast(handle); + QFontEngineQPA *engine = new QFontEngineQPA(fontDef,*fileDataPtr); + //qDebug() << fontDef.pixelSize << fontDef.weight << fontDef.style << fontDef.stretch << fontDef.styleHint << fontDef.styleStrategy << fontDef.family << script; + return engine; +} + +/*! + +*/ +QStringList QPlatformFontDatabase::fallbacksForFamily(const QString family, const QFont::Style &style, const QFont::StyleHint &styleHint, const QUnicodeTables::Script &script) const +{ + Q_UNUSED(family); + Q_UNUSED(style); + Q_UNUSED(styleHint); + Q_UNUSED(script); + return QStringList(); +} + +/*! + Adds an application font. Returns a list of family names, or an empty list if the font could + not be added +*/ +QStringList QPlatformFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName) +{ + Q_UNUSED(fontData); + Q_UNUSED(fileName); + + qWarning("This plugin does not support application fonts"); + return QStringList(); +} + +/*! + +*/ +void QPlatformFontDatabase::releaseHandle(void *handle) +{ + QByteArray *fileDataPtr = static_cast(handle); + delete fileDataPtr; +} + +/*! + +*/ +QString QPlatformFontDatabase::fontDir() const +{ + QString fontpath = QString::fromLocal8Bit(qgetenv("QT_QPA_FONTDIR")); + if (fontpath.isEmpty()) { +#ifndef QT_NO_SETTINGS + fontpath = QLibraryInfo::location(QLibraryInfo::LibrariesPath); + fontpath += QLatin1String("/fonts"); +#endif + } + + return fontpath; +} + +/*! + \class QPlatformFontDatabase + \brief The QPlatformFontDatabase makes it possible to customize how fonts are picked up, and + and how they are rendered + + \ingroup painting + + QPlatformFontDatabase is the superclass which is intended to let platform implementations use + native font handling. + + Qt has its internal fontdatabase which it uses to pick up available fonts. To be able + to populate this database subclass this class, and reimplement populateFontDatabase(). + + Use the function registerFont to populate the internal fontdatabase. + + Sometimes a specified font does not have the required glyphs, then the fallbackForFamily + function is called. + + \sa QSupportedWritingSystems +*/ +QT_END_NAMESPACE diff --git a/src/gui/text/qplatformfontdatabase_qpa.h b/src/gui/text/qplatformfontdatabase_qpa.h new file mode 100644 index 0000000000..e0e4f04d89 --- /dev/null +++ b/src/gui/text/qplatformfontdatabase_qpa.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPLATFORMFONTDATABASE_QPA_H +#define QPLATFORMFONTDATABASE_QPA_H + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QWritingSystemsPrivate; + +class Q_GUI_EXPORT QSupportedWritingSystems +{ +public: + + QSupportedWritingSystems(); + QSupportedWritingSystems(const QSupportedWritingSystems &other); + QSupportedWritingSystems &operator=(const QSupportedWritingSystems &other); + ~QSupportedWritingSystems(); + + void setSupported(QFontDatabase::WritingSystem, bool supported = true); + bool supported(QFontDatabase::WritingSystem) const; + +private: + void detach(); + + QWritingSystemsPrivate *d; + + friend Q_GUI_EXPORT bool operator==(const QSupportedWritingSystems &, const QSupportedWritingSystems &); + friend Q_GUI_EXPORT bool operator!=(const QSupportedWritingSystems &, const QSupportedWritingSystems &); +}; + +Q_GUI_EXPORT bool operator==(const QSupportedWritingSystems &, const QSupportedWritingSystems &); +Q_GUI_EXPORT bool operator!=(const QSupportedWritingSystems &, const QSupportedWritingSystems &); + +class QFontRequestPrivate; + +class Q_GUI_EXPORT QPlatformFontDatabase +{ +public: + virtual void populateFontDatabase(); + virtual QFontEngine *fontEngine(const QFontDef &fontDef, QUnicodeTables::Script script, void *handle); + virtual QStringList fallbacksForFamily(const QString family, const QFont::Style &style, const QFont::StyleHint &styleHint, const QUnicodeTables::Script &script) const; + virtual QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName); + virtual void releaseHandle(void *handle); + + virtual QString fontDir() const; + + //callback + static void registerQPF2Font(const QByteArray &dataArray, void *handle); + static void registerFont(const QString &familyname, const QString &foundryname, QFont::Weight weight, + QFont::Style style, QFont::Stretch stretch, bool antialiased, bool scalable, int pixelSize, + const QSupportedWritingSystems &writingSystems, void *handle); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QPLATFORMFONTDATABASE_QPA_H diff --git a/src/gui/text/qrawfont.cpp b/src/gui/text/qrawfont.cpp new file mode 100644 index 0000000000..4a715c27cc --- /dev/null +++ b/src/gui/text/qrawfont.cpp @@ -0,0 +1,612 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobal.h" + +#if !defined(QT_NO_RAWFONT) + +#include "qrawfont.h" +#include "qrawfont_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QRawFont + \brief The QRawFont class provides access to a single physical instance of a font. + \since 4.8 + + \ingroup text + \mainclass + + \note QRawFont is a low level class. For most purposes QFont is a more appropriate class. + + Most commonly, when presenting text in a user interface, the exact fonts used + to render the characters is to some extent unknown. This can be the case for several + reasons: For instance, the actual, physical fonts present on the target system could be + unexpected to the developers, or the text could contain user selected styles, sizes or + writing systems that are not supported by font chosen in the code. + + Therefore, Qt's QFont class really represents a query for fonts. When text is interpreted, + Qt will do its best to match the text to the query, but depending on the support, different + fonts can be used behind the scenes. + + For most use cases, this is both expected and necessary, as it minimizes the possibility of + text in the user interface being undisplayable. In some cases, however, more direct control + over the process might be useful. It is for these use cases the QRawFont class exists. + + A QRawFont object represents a single, physical instance of a given font in a given pixel size. + I.e. in the typical case it represents a set of TrueType or OpenType font tables and uses a + user specified pixel size to convert metrics into logical pixel units. In can be used in + combination with the QGlyphs class to draw specific glyph indexes at specific positions, and + also have accessors to some relevant data in the physical font. + + QRawFont only provides support for the main font technologies: GDI and DirectWrite on Windows + platforms, FreeType on Symbian and Linux platforms and CoreText on Mac OS X. For other + font back-ends, the APIs will be disabled. + + QRawFont can be constructed in a number of ways: + \list + \o \l It can be constructed by calling QTextLayout::glyphs() or QTextFragment::glyphs(). The + returned QGlyphs objects will contain QRawFont objects which represent the actual fonts + used to render each portion of the text. + \o \l It can be constructed by passing a QFont object to QRawFont::fromFont(). The function + will return a QRawFont object representing the font that will be selected as response to + the QFont query and the selected writing system. + \o \l It can be constructed by passing a file name or QByteArray directly to the QRawFont + constructor, or by calling loadFromFile() or loadFromData(). In this case, the + font will not be registered in QFontDatabase, and it will not be available as part of + regular font selection. + \endlist + + QRawFont is considered local to the thread in which it is constructed (either using a + constructor, or by calling loadFromData() or loadFromFile()). The QRawFont cannot be moved to a + different thread, but will have to be recreated in the thread in question. + + \note For the requirement of caching glyph indexes and font selections for static text to avoid + reshaping and relayouting in the inner loop of an application, a better choice is the QStaticText + class, since it optimizes the memory cost of the cache and also provides the possibility of paint + engine specific caches for an additional speed-up. +*/ + +/*! + \enum QRawFont::AntialiasingType + + This enum represents the different ways a glyph can be rasterized in the function + alphaMapForGlyph(). + + \value PixelAntialiasing Will rasterize by measuring the coverage of the shape on whole pixels. + The returned image contains the alpha values of each pixel based on the coverage of + the glyph shape. + \value SubPixelAntialiasing Will rasterize by measuring the coverage of each subpixel, + returning a separate alpha value for each of the red, green and blue components of + each pixel. +*/ + +/*! + Constructs an invalid QRawFont. +*/ +QRawFont::QRawFont() + : d(new QRawFontPrivate) +{ +} + +/*! + Constructs a QRawFont representing the font contained in the file referenced by \a fileName, + with \a pixelSize size in pixels, and the selected \a hintingPreference. + + \note The referenced file must contain a TrueType or OpenType font. +*/ +QRawFont::QRawFont(const QString &fileName, + int pixelSize, + QFont::HintingPreference hintingPreference) + : d(new QRawFontPrivate) +{ + loadFromFile(fileName, pixelSize, hintingPreference); +} + +/*! + Constructs a QRawFont representing the font contained in \a fontData. + + \note The data must contain a TrueType or OpenType font. +*/ +QRawFont::QRawFont(const QByteArray &fontData, + int pixelSize, + QFont::HintingPreference hintingPreference) + : d(new QRawFontPrivate) +{ + loadFromData(fontData, pixelSize, hintingPreference); +} + +/*! + Creates a QRawFont which is a copy of \a other. +*/ +QRawFont::QRawFont(const QRawFont &other) +{ + d = other.d; +} + +/*! + Destroys the QRawFont +*/ +QRawFont::~QRawFont() +{ +} + +/*! + Assigns \a other to this QRawFont. +*/ +QRawFont &QRawFont::operator=(const QRawFont &other) +{ + d = other.d; + return *this; +} + +/*! + Returns true if the QRawFont is valid and false otherwise. +*/ +bool QRawFont::isValid() const +{ + Q_ASSERT(d->thread == 0 || d->thread == QThread::currentThread()); + return d->fontEngine != 0; +} + +/*! + Replaces the current QRawFont with the contents of the file references by \a fileName. + + The file must reference a TrueType or OpenType font. + + \sa loadFromData() +*/ +void QRawFont::loadFromFile(const QString &fileName, + int pixelSize, + QFont::HintingPreference hintingPreference) +{ + QFile file(fileName); + if (file.open(QIODevice::ReadOnly)) + loadFromData(file.readAll(), pixelSize, hintingPreference); +} + +/*! + Replaces the current QRawFont with the contents of \a fontData. + + The \a fontData must contain a TrueType or OpenType font. + + \sa loadFromFile() +*/ +void QRawFont::loadFromData(const QByteArray &fontData, + int pixelSize, + QFont::HintingPreference hintingPreference) +{ + detach(); + d->cleanUp(); + d->hintingPreference = hintingPreference; + d->thread = QThread::currentThread(); + d->platformLoadFromData(fontData, pixelSize, hintingPreference); +} + +/*! + This function returns a rasterized image of the glyph at a given \a glyphIndex in the underlying + font, if the QRawFont is valid, otherwise it will return an invalid QImage. + + If \a antialiasingType is set to QRawFont::SubPixelAntialiasing, then the resulting image will be + in QImage::Format_RGB32 and the RGB values of each pixel will represent the subpixel opacities of + the pixel in the rasterization of the glyph. Otherwise, the image will be in the format of + QImage::Format_A8 and each pixel will contain the opacity of the pixel in the rasterization. + + \sa pathForGlyph(), QPainter::drawGlyphs() +*/ +QImage QRawFont::alphaMapForGlyph(quint32 glyphIndex, AntialiasingType antialiasingType, + const QTransform &transform) const +{ + if (!isValid()) + return QImage(); + + if (antialiasingType == SubPixelAntialiasing) + return d->fontEngine->alphaRGBMapForGlyph(glyphIndex, QFixed(), 0, transform); + else + return d->fontEngine->alphaMapForGlyph(glyphIndex, QFixed(), transform); +} + +/*! + This function returns the shape of the glyph at a given \a glyphIndex in the underlying font + if the QRawFont is valid. Otherwise, it returns an empty QPainterPath. + + The returned glyph will always be unhinted. + + \sa alphaMapForGlyph(), QPainterPath::addText() +*/ +QPainterPath QRawFont::pathForGlyph(quint32 glyphIndex) const +{ + if (!isValid()) + return QPainterPath(); + + QFixedPoint position; + QPainterPath path; + d->fontEngine->addGlyphsToPath(&glyphIndex, &position, 1, &path, 0); + return path; +} + +/*! + Returns true if this QRawFont is equal to \a other. Otherwise, returns false. +*/ +bool QRawFont::operator==(const QRawFont &other) const +{ + return d->fontEngine == other.d->fontEngine; +} + +/*! + Returns the ascent of this QRawFont in pixel units. + + \sa QFontMetricsF::ascent() +*/ +qreal QRawFont::ascent() const +{ + if (!isValid()) + return 0.0; + + return d->fontEngine->ascent().toReal(); +} + +/*! + Returns the descent of this QRawFont in pixel units. + + \sa QFontMetricsF::descent() +*/ +qreal QRawFont::descent() const +{ + if (!isValid()) + return 0.0; + + return d->fontEngine->descent().toReal(); +} + +/*! + Returns the pixel size set for this QRawFont. The pixel size affects how glyphs are + rasterized, the size of glyphs returned by pathForGlyph(), and is used to convert + internal metrics from design units to logical pixel units. + + \sa setPixelSize() +*/ +int QRawFont::pixelSize() const +{ + if (!isValid()) + return -1; + + return d->fontEngine->fontDef.pixelSize; +} + +/*! + Returns the number of design units define the width and height of the em square + for this QRawFont. This value is used together with the pixel size when converting design metrics + to pixel units, as the internal metrics are specified in design units and the pixel size gives + the size of 1 em in pixels. + + \sa pixelSize(), setPixelSize() +*/ +qreal QRawFont::unitsPerEm() const +{ + if (!isValid()) + return 0.0; + + return d->fontEngine->emSquareSize().toReal(); +} + +/*! + Returns the family name of this QRawFont. +*/ +QString QRawFont::familyName() const +{ + if (!isValid()) + return QString(); + + return d->fontEngine->fontDef.family; +} + +/*! + Returns the style of this QRawFont. + + \sa QFont::style() +*/ +QFont::Style QRawFont::style() const +{ + if (!isValid()) + return QFont::StyleNormal; + + return QFont::Style(d->fontEngine->fontDef.style); +} + +/*! + Returns the weight of this QRawFont. + + \sa QFont::weight() +*/ +int QRawFont::weight() const +{ + if (!isValid()) + return -1; + + return int(d->fontEngine->fontDef.weight); +} + +/*! + Converts a string of unicode points to glyph indexes using the CMAP table in the + underlying font. Note that in cases where there are other tables in the font that affect the + shaping of the text, the returned glyph indexes will not correctly represent the rendering + of the text. To get the correctly shaped text, you can use QTextLayout to lay out and shape the + text, and then call QTextLayout::glyphs() to get the set of glyph index list and QRawFont pairs. + + \sa advancesForGlyphIndexes(), QGlyphs, QTextLayout::glyphs(), QTextFragment::glyphs() +*/ +QVector QRawFont::glyphIndexesForString(const QString &text) const +{ + if (!isValid()) + return QVector(); + + int nglyphs = text.size(); + QVarLengthGlyphLayoutArray glyphs(nglyphs); + if (!d->fontEngine->stringToCMap(text.data(), text.size(), &glyphs, &nglyphs, + QTextEngine::GlyphIndicesOnly)) { + glyphs.resize(nglyphs); + if (!d->fontEngine->stringToCMap(text.data(), text.size(), &glyphs, &nglyphs, + QTextEngine::GlyphIndicesOnly)) { + Q_ASSERT_X(false, Q_FUNC_INFO, "stringToCMap shouldn't fail twice"); + return QVector(); + } + } + + QVector glyphIndexes; + for (int i=0; i QRawFont::advancesForGlyphIndexes(const QVector &glyphIndexes) const +{ + if (!isValid()) + return QVector(); + + int numGlyphs = glyphIndexes.size(); + QVarLengthGlyphLayoutArray glyphs(numGlyphs); + qMemCopy(glyphs.glyphs, glyphIndexes.data(), numGlyphs * sizeof(quint32)); + + d->fontEngine->recalcAdvances(&glyphs, 0); + + QVector advances; + for (int i=0; ihintingPreference; +} + +/*! + Retrieves the sfnt table named \a tagName from the underlying physical font, or an empty + byte array if no such table was found. The returned font table's byte order is Big Endian, like + the sfnt format specifies. The \a tagName must be four characters long and should be formatted + in the default endianness of the current platform. +*/ +QByteArray QRawFont::fontTable(const char *tagName) const +{ + if (!isValid()) + return QByteArray(); + + const quint32 *tagId = reinterpret_cast(tagName); + return d->fontEngine->getSfntTable(qToBigEndian(*tagId)); +} + +// From qfontdatabase.cpp +extern QList qt_determine_writing_systems_from_truetype_bits(quint32 unicodeRange[4], quint32 codePageRange[2]); + +/*! + Returns a list of writing systems supported by the font according to designer supplied + information in the font file. Please note that this does not guarantee support for a + specific unicode point in the font. You can use the supportsCharacter() to check support + for a single, specific character. + + \note The list is determined based on the unicode ranges and codepage ranges set in the font's + OS/2 table and requires such a table to be present in the underlying font file. + + \sa supportsCharacter() +*/ +QList QRawFont::supportedWritingSystems() const +{ + if (isValid()) { + QByteArray os2Table = fontTable("OS/2"); + if (!os2Table.isEmpty() && os2Table.size() > 86) { + char *data = os2Table.data(); + quint32 *bigEndianUnicodeRanges = reinterpret_cast(data + 42); + quint32 *bigEndianCodepageRanges = reinterpret_cast(data + 78); + + quint32 unicodeRanges[4]; + quint32 codepageRanges[2]; + + for (int i=0; i<4; ++i) { + if (i < 2) + codepageRanges[i] = qFromBigEndian(bigEndianCodepageRanges[i]); + unicodeRanges[i] = qFromBigEndian(bigEndianUnicodeRanges[i]); + } + + return qt_determine_writing_systems_from_truetype_bits(unicodeRanges, codepageRanges); + } + } + + return QList(); +} + +/*! + Returns true if the font has a glyph that corresponds to the given \a character. + + \sa supportedWritingSystems() +*/ +bool QRawFont::supportsCharacter(const QChar &character) const +{ + if (!isValid()) + return false; + + return d->fontEngine->canRender(&character, 1); +} + +/*! + Returns true if the font has a glyph that corresponds to the UCS-4 encoded character \a ucs4. + + \sa supportedWritingSystems() +*/ +bool QRawFont::supportsCharacter(quint32 ucs4) const +{ + if (!isValid()) + return false; + + QString str = QString::fromUcs4(&ucs4, 1); + return d->fontEngine->canRender(str.constData(), str.size()); +} + +// qfontdatabase.cpp +extern int qt_script_for_writing_system(QFontDatabase::WritingSystem writingSystem); + +/*! + Fetches the physical representation based on a \a font query. The physical font returned is + the font that will be preferred by Qt in order to display text in the selected \a writingSystem. +*/ +QRawFont QRawFont::fromFont(const QFont &font, QFontDatabase::WritingSystem writingSystem) +{ +#if defined(Q_WS_MAC) + QTextLayout layout(QFontDatabase::writingSystemSample(writingSystem), font); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + QList list = layout.glyphs(); + if (list.size()) { + // Pick the one matches the family name we originally requested, + // if none of them match, just pick the first one + for (int i = 0; i < list.size(); i++) { + QGlyphs glyphs = list.at(i); + QRawFont rawfont = glyphs.font(); + if (rawfont.familyName() == font.family()) + return rawfont; + } + return list.at(0).font(); + } + return QRawFont(); +#else + QFontPrivate *font_d = QFontPrivate::get(font); + int script = qt_script_for_writing_system(writingSystem); + QFontEngine *fe = font_d->engineForScript(script); + + if (fe != 0 && fe->type() == QFontEngine::Multi) { + QFontEngineMulti *multiEngine = static_cast(fe); + fe = multiEngine->engine(0); + if (fe == 0) { + multiEngine->loadEngine(0); + fe = multiEngine->engine(0); + } + } + + if (fe != 0) { + QRawFont rawFont; + rawFont.d.data()->fontEngine = fe; + rawFont.d.data()->fontEngine->ref.ref(); + rawFont.d.data()->hintingPreference = font.hintingPreference(); + return rawFont; + } else { + return QRawFont(); + } +#endif +} + +/*! + Sets the pixel size with which this font should be rendered to \a pixelSize. +*/ +void QRawFont::setPixelSize(int pixelSize) +{ + detach(); + d->platformSetPixelSize(pixelSize); +} + +/*! + \internal +*/ +void QRawFont::detach() +{ + if (d->ref != 1) + d.detach(); +} + +/*! + \internal +*/ +void QRawFontPrivate::cleanUp() +{ + platformCleanUp(); + if (fontEngine != 0) { + fontEngine->ref.deref(); + if (fontEngine->cache_count == 0 && fontEngine->ref == 0) + delete fontEngine; + fontEngine = 0; + } + hintingPreference = QFont::PreferDefaultHinting; +} + +#endif // QT_NO_RAWFONT + +QT_END_NAMESPACE diff --git a/src/gui/text/qrawfont.h b/src/gui/text/qrawfont.h new file mode 100644 index 0000000000..96dc838ede --- /dev/null +++ b/src/gui/text/qrawfont.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QRAWFONT_H +#define QRAWFONT_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(QT_NO_RAWFONT) + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QRawFontPrivate; +class Q_GUI_EXPORT QRawFont +{ +public: + enum AntialiasingType { + PixelAntialiasing, + SubPixelAntialiasing + }; + + QRawFont(); + QRawFont(const QString &fileName, + int pixelSize, + QFont::HintingPreference hintingPreference = QFont::PreferDefaultHinting); + QRawFont(const QByteArray &fontData, + int pixelSize, + QFont::HintingPreference hintingPreference = QFont::PreferDefaultHinting); + QRawFont(const QRawFont &other); + ~QRawFont(); + + bool isValid() const; + + QRawFont &operator=(const QRawFont &other); + bool operator==(const QRawFont &other) const; + + QString familyName() const; + + QFont::Style style() const; + int weight() const; + + QVector glyphIndexesForString(const QString &text) const; + QVector advancesForGlyphIndexes(const QVector &glyphIndexes) const; + + QImage alphaMapForGlyph(quint32 glyphIndex, + AntialiasingType antialiasingType = SubPixelAntialiasing, + const QTransform &transform = QTransform()) const; + QPainterPath pathForGlyph(quint32 glyphIndex) const; + + void setPixelSize(int pixelSize); + int pixelSize() const; + + QFont::HintingPreference hintingPreference() const; + + qreal ascent() const; + qreal descent() const; + + qreal unitsPerEm() const; + + void loadFromFile(const QString &fileName, + int pixelSize, + QFont::HintingPreference hintingPreference); + + void loadFromData(const QByteArray &fontData, + int pixelSize, + QFont::HintingPreference hintingPreference); + + bool supportsCharacter(quint32 ucs4) const; + bool supportsCharacter(const QChar &character) const; + QList supportedWritingSystems() const; + + QByteArray fontTable(const char *tagName) const; + + static QRawFont fromFont(const QFont &font, + QFontDatabase::WritingSystem writingSystem = QFontDatabase::Any); + +private: + friend class QRawFontPrivate; + + void detach(); + + QExplicitlySharedDataPointer d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_RAWFONT + +#endif // QRAWFONT_H diff --git a/src/gui/text/qrawfont_ft.cpp b/src/gui/text/qrawfont_ft.cpp new file mode 100644 index 0000000000..eefbd92118 --- /dev/null +++ b/src/gui/text/qrawfont_ft.cpp @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#if !defined(QT_NO_RAWFONT) + +#include "qrawfont_p.h" +#include "qfontengine_ft_p.h" + +#if defined(Q_WS_X11) +# include "qfontengine_x11_p.h" +#endif + +QT_BEGIN_NAMESPACE + +class QFontEngineFTRawFont + +#if defined(Q_WS_X11) + : public QFontEngineX11FT +#else + : public QFontEngineFT +#endif + +{ +public: + QFontEngineFTRawFont(const QFontDef &fontDef) +#if defined(Q_WS_X11) + : QFontEngineX11FT(fontDef) +#else + : QFontEngineFT(fontDef) +#endif + { + } + + void updateFamilyNameAndStyle() + { + fontDef.family = QString::fromAscii(freetype->face->family_name); + + if (freetype->face->style_flags & FT_STYLE_FLAG_ITALIC) + fontDef.style = QFont::StyleItalic; + + if (freetype->face->style_flags & FT_STYLE_FLAG_BOLD) + fontDef.weight = QFont::Bold; + } + + bool initFromData(const QByteArray &fontData) + { + FaceId faceId; + faceId.filename = ""; + faceId.index = 0; + + return init(faceId, true, Format_None, fontData); + } + + bool initFromFontEngine(QFontEngine *oldFontEngine) + { + QFontEngineFT *fe = static_cast(oldFontEngine); + + // Increase the reference of this QFreetypeFace since one more QFontEngineFT + // will be using it + fe->freetype->ref.ref(); + if (!init(fe->faceId(), fe->antialias, fe->defaultFormat, fe->freetype)) + return false; + + default_load_flags = fe->default_load_flags; + default_hint_style = fe->default_hint_style; + antialias = fe->antialias; + transform = fe->transform; + embolden = fe->embolden; + subpixelType = fe->subpixelType; + lcdFilterType = fe->lcdFilterType; + canUploadGlyphsToServer = fe->canUploadGlyphsToServer; + embeddedbitmap = fe->embeddedbitmap; + +#if defined(Q_WS_X11) + xglyph_format = static_cast(fe)->xglyph_format; +#endif + return true; + } +}; + + +void QRawFontPrivate::platformCleanUp() +{ + // Font engine handles all resources +} + +void QRawFontPrivate::platformLoadFromData(const QByteArray &fontData, int pixelSize, + QFont::HintingPreference hintingPreference) +{ + Q_ASSERT(fontEngine == 0); + + QFontDef fontDef; + fontDef.pixelSize = pixelSize; + + QFontEngineFTRawFont *fe = new QFontEngineFTRawFont(fontDef); + if (!fe->initFromData(fontData)) { + delete fe; + return; + } + + fe->updateFamilyNameAndStyle(); + + switch (hintingPreference) { + case QFont::PreferNoHinting: + fe->setDefaultHintStyle(QFontEngineFT::HintNone); + break; + case QFont::PreferFullHinting: + fe->setDefaultHintStyle(QFontEngineFT::HintFull); + break; + case QFont::PreferVerticalHinting: + fe->setDefaultHintStyle(QFontEngineFT::HintLight); + break; + default: + // Leave it as it is + break; + } + + fontEngine = fe; + fontEngine->ref.ref(); +} + +void QRawFontPrivate::platformSetPixelSize(int pixelSize) +{ + if (fontEngine == NULL) + return; + + QFontEngine *oldFontEngine = fontEngine; + + QFontDef fontDef; + fontDef.pixelSize = pixelSize; + QFontEngineFTRawFont *fe = new QFontEngineFTRawFont(fontDef); + if (!fe->initFromFontEngine(oldFontEngine)) { + delete fe; + return; + } + + fontEngine = fe; + fontEngine->fontDef = oldFontEngine->fontDef; + fontEngine->fontDef.pixelSize = pixelSize; + fontEngine->ref.ref(); + Q_ASSERT(fontEngine != oldFontEngine); + oldFontEngine->ref.deref(); + if (oldFontEngine->cache_count == 0 && oldFontEngine->ref == 0) + delete oldFontEngine; +} + +QT_END_NAMESPACE + +#endif // QT_NO_RAWFONT diff --git a/src/gui/text/qrawfont_mac.cpp b/src/gui/text/qrawfont_mac.cpp new file mode 100644 index 0000000000..56005c61f5 --- /dev/null +++ b/src/gui/text/qrawfont_mac.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#if !defined(QT_NO_RAWFONT) + +#include "qrawfont_p.h" +#include "qfontengine_coretext_p.h" + +QT_BEGIN_NAMESPACE + +void QRawFontPrivate::platformCleanUp() +{ +} + +extern int qt_defaultDpi(); + +void QRawFontPrivate::platformLoadFromData(const QByteArray &fontData, + int pixelSize, + QFont::HintingPreference hintingPreference) +{ + // Mac OS X ignores it + Q_UNUSED(hintingPreference); + + QCFType dataProvider = CGDataProviderCreateWithData(NULL, + fontData.constData(), fontData.size(), NULL); + + CGFontRef cgFont = CGFontCreateWithDataProvider(dataProvider); + + if (cgFont == NULL) { + qWarning("QRawFont::platformLoadFromData: CGFontCreateWithDataProvider failed"); + } else { + QFontDef def; + def.pixelSize = pixelSize; + def.pointSize = pixelSize * 72.0 / qt_defaultDpi(); + fontEngine = new QCoreTextFontEngine(cgFont, def); + CFRelease(cgFont); + fontEngine->ref.ref(); + } +} + +void QRawFontPrivate::platformSetPixelSize(int pixelSize) +{ + if (fontEngine == NULL) + return; + + QFontEngine *oldFontEngine = fontEngine; + + QFontDef fontDef = oldFontEngine->fontDef; + fontDef.pixelSize = pixelSize; + fontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi(); + + QCoreTextFontEngine *ctFontEngine = static_cast(oldFontEngine); + Q_ASSERT(ctFontEngine->cgFont); + + fontEngine = new QCoreTextFontEngine(ctFontEngine->cgFont, fontDef); + fontEngine->ref.ref(); + Q_ASSERT(fontEngine != oldFontEngine); + oldFontEngine->ref.deref(); + if (oldFontEngine->cache_count == 0 && oldFontEngine->ref == 0) + delete oldFontEngine; +} + +QT_END_NAMESPACE + +#endif // QT_NO_RAWFONT diff --git a/src/gui/text/qrawfont_p.h b/src/gui/text/qrawfont_p.h new file mode 100644 index 0000000000..f9a9ab55cd --- /dev/null +++ b/src/gui/text/qrawfont_p.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QRAWFONTPRIVATE_P_H +#define QRAWFONTPRIVATE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qrawfont.h" +#include "qfontengine_p.h" +#include + +#if !defined(QT_NO_RAWFONT) + +QT_BEGIN_NAMESPACE + +namespace { class CustomFontFileLoader; } +class Q_AUTOTEST_EXPORT QRawFontPrivate +{ +public: + QRawFontPrivate() + : fontEngine(0) + , hintingPreference(QFont::PreferDefaultHinting) + , thread(0) +#if defined(Q_WS_WIN) + , fontHandle(NULL) + , ptrAddFontMemResourceEx(NULL) + , ptrRemoveFontMemResourceEx(NULL) +#endif + {} + + QRawFontPrivate(const QRawFontPrivate &other) + : hintingPreference(other.hintingPreference) + , thread(other.thread) +#if defined(Q_WS_WIN) + , fontHandle(NULL) + , ptrAddFontMemResourceEx(other.ptrAddFontMemResourceEx) + , ptrRemoveFontMemResourceEx(other.ptrRemoveFontMemResourceEx) + , uniqueFamilyName(other.uniqueFamilyName) +#endif + { + fontEngine = other.fontEngine; + if (fontEngine != 0) + fontEngine->ref.ref(); + } + + ~QRawFontPrivate() + { + Q_ASSERT(ref == 0); + cleanUp(); + } + + void cleanUp(); + void platformCleanUp(); + void platformLoadFromData(const QByteArray &fontData, + int pixelSize, + QFont::HintingPreference hintingPreference); + void platformSetPixelSize(int pixelSize); + + static QRawFontPrivate *get(const QRawFont &font) { return font.d.data(); } + + QFontEngine *fontEngine; + QFont::HintingPreference hintingPreference; + QThread *thread; + QAtomicInt ref; + +#if defined(Q_WS_WIN) + HANDLE fontHandle; + + typedef HANDLE (WINAPI *PtrAddFontMemResourceEx)(PVOID, DWORD, PVOID, DWORD *); + typedef BOOL (WINAPI *PtrRemoveFontMemResourceEx)(HANDLE); + + PtrAddFontMemResourceEx ptrAddFontMemResourceEx; + PtrRemoveFontMemResourceEx ptrRemoveFontMemResourceEx; + + QString uniqueFamilyName; + +#endif // Q_WS_WIN +}; + +QT_END_NAMESPACE + +#endif // QT_NO_RAWFONT + +#endif // QRAWFONTPRIVATE_P_H diff --git a/src/gui/text/qrawfont_win.cpp b/src/gui/text/qrawfont_win.cpp new file mode 100644 index 0000000000..fb5c6f46b6 --- /dev/null +++ b/src/gui/text/qrawfont_win.cpp @@ -0,0 +1,750 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qrawfont_p.h" +#include + +#if !defined(QT_NO_DIRECTWRITE) +# include "qfontenginedirectwrite_p.h" +# include +#endif + +#if !defined(QT_NO_RAWFONT) + +QT_BEGIN_NAMESPACE + +namespace { + + template + struct BigEndian + { + quint8 data[sizeof(T)]; + + operator T() const + { + T littleEndian = 0; + for (int i=0; i &operator=(const T &t) + { + for (int i=0; i> (sizeof(T) - i - 1) * 8) & 0xff); + } + + return *this; + } + }; + +# pragma pack(1) + + // Common structure for all formats of the "name" table + struct NameTable + { + BigEndian format; + BigEndian count; + BigEndian stringOffset; + }; + + struct NameRecord + { + BigEndian platformID; + BigEndian encodingID; + BigEndian languageID; + BigEndian nameID; + BigEndian length; + BigEndian offset; + }; + + struct OffsetSubTable + { + BigEndian scalerType; + BigEndian numTables; + BigEndian searchRange; + BigEndian entrySelector; + BigEndian rangeShift; + }; + + struct TableDirectory + { + BigEndian identifier; + BigEndian checkSum; + BigEndian offset; + BigEndian length; + }; + + struct OS2Table + { + BigEndian version; + BigEndian avgCharWidth; + BigEndian weightClass; + BigEndian widthClass; + BigEndian type; + BigEndian subscriptXSize; + BigEndian subscriptYSize; + BigEndian subscriptXOffset; + BigEndian subscriptYOffset; + BigEndian superscriptXSize; + BigEndian superscriptYSize; + BigEndian superscriptXOffset; + BigEndian superscriptYOffset; + BigEndian strikeOutSize; + BigEndian strikeOutPosition; + BigEndian familyClass; + quint8 panose[10]; + BigEndian unicodeRanges[4]; + quint8 vendorID[4]; + BigEndian selection; + BigEndian firstCharIndex; + BigEndian lastCharIndex; + BigEndian typoAscender; + BigEndian typoDescender; + BigEndian typoLineGap; + BigEndian winAscent; + BigEndian winDescent; + BigEndian codepageRanges[2]; + BigEndian height; + BigEndian capHeight; + BigEndian defaultChar; + BigEndian breakChar; + BigEndian maxContext; + }; + +# pragma pack() + + class EmbeddedFont + { + public: + EmbeddedFont(const QByteArray &fontData); + + QString changeFamilyName(const QString &newFamilyName); + QByteArray data() const { return m_fontData; } + TableDirectory *tableDirectoryEntry(const QByteArray &tagName); + QString familyName(TableDirectory *nameTableDirectory = 0); + + private: + QByteArray m_fontData; + }; + + EmbeddedFont::EmbeddedFont(const QByteArray &fontData) : m_fontData(fontData) + { + } + + TableDirectory *EmbeddedFont::tableDirectoryEntry(const QByteArray &tagName) + { + Q_ASSERT(tagName.size() == 4); + + const BigEndian *tagIdPtr = + reinterpret_cast *>(tagName.constData()); + quint32 tagId = *tagIdPtr; + + OffsetSubTable *offsetSubTable = reinterpret_cast(m_fontData.data()); + TableDirectory *tableDirectory = reinterpret_cast(offsetSubTable + 1); + + TableDirectory *nameTableDirectoryEntry = 0; + for (int i=0; inumTables; ++i, ++tableDirectory) { + if (tableDirectory->identifier == tagId) { + nameTableDirectoryEntry = tableDirectory; + break; + } + } + + return nameTableDirectoryEntry; + } + + QString EmbeddedFont::familyName(TableDirectory *nameTableDirectoryEntry) + { + QString name; + + if (nameTableDirectoryEntry == 0) + nameTableDirectoryEntry = tableDirectoryEntry("name"); + + if (nameTableDirectoryEntry != 0) { + NameTable *nameTable = reinterpret_cast(m_fontData.data() + + nameTableDirectoryEntry->offset); + NameRecord *nameRecord = reinterpret_cast(nameTable + 1); + for (int i=0; icount; ++i, ++nameRecord) { + if (nameRecord->nameID == 1 + && nameRecord->platformID == 3 // Windows + && nameRecord->languageID == 0x0409) { // US English + const void *ptr = reinterpret_cast(nameTable) + + nameTable->stringOffset + + nameRecord->offset; + + const BigEndian *s = reinterpret_cast *>(ptr); + for (int j=0; jlength / sizeof(quint16); ++j) + name += QChar(s[j]); + + break; + } + } + } + + return name; + } + + QString EmbeddedFont::changeFamilyName(const QString &newFamilyName) + { + TableDirectory *nameTableDirectoryEntry = tableDirectoryEntry("name"); + if (nameTableDirectoryEntry == 0) + return QString(); + + QString oldFamilyName = familyName(nameTableDirectoryEntry); + + // Reserve size for name table header, five required name records and string + const int requiredRecordCount = 5; + quint16 nameIds[requiredRecordCount] = { 1, 2, 3, 4, 6 }; + + int sizeOfHeader = sizeof(NameTable) + sizeof(NameRecord) * requiredRecordCount; + int newFamilyNameSize = newFamilyName.size() * sizeof(quint16); + + const QString regularString = QString::fromLatin1("Regular"); + int regularStringSize = regularString.size() * sizeof(quint16); + + // Align table size of table to 32 bits (pad with 0) + int fullSize = ((sizeOfHeader + newFamilyNameSize + regularStringSize) & ~3) + 4; + + QByteArray newNameTable(fullSize, char(0)); + + { + NameTable *nameTable = reinterpret_cast(newNameTable.data()); + nameTable->count = requiredRecordCount; + nameTable->stringOffset = sizeOfHeader; + + NameRecord *nameRecord = reinterpret_cast(nameTable + 1); + for (int i=0; inameID = nameIds[i]; + nameRecord->encodingID = 1; + nameRecord->languageID = 0x0409; + nameRecord->platformID = 3; + nameRecord->length = newFamilyNameSize; + + // Special case for sub-family + if (nameIds[i] == 4) { + nameRecord->offset = newFamilyNameSize; + nameRecord->length = regularStringSize; + } + } + + // nameRecord now points to string data + BigEndian *stringStorage = reinterpret_cast *>(nameRecord); + const quint16 *sourceString = newFamilyName.utf16(); + for (int i=0; i(newNameTable.data()); + quint32 *tableEnd = reinterpret_cast(newNameTable.data() + fullSize); + + quint32 checkSum = 0; + while (p < tableEnd) + checkSum += *(p++); + + nameTableDirectoryEntry->checkSum = checkSum; + nameTableDirectoryEntry->offset = m_fontData.size(); + nameTableDirectoryEntry->length = fullSize; + + m_fontData.append(newNameTable); + + return oldFamilyName; + } + +#if !defined(QT_NO_DIRECTWRITE) + + class DirectWriteFontFileStream: public IDWriteFontFileStream + { + public: + DirectWriteFontFileStream(const QByteArray &fontData) + : m_fontData(fontData) + , m_referenceCount(0) + { + } + + ~DirectWriteFontFileStream() + { + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **object); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE ReadFileFragment(const void **fragmentStart, UINT64 fileOffset, + UINT64 fragmentSize, OUT void **fragmentContext); + void STDMETHODCALLTYPE ReleaseFileFragment(void *fragmentContext); + HRESULT STDMETHODCALLTYPE GetFileSize(OUT UINT64 *fileSize); + HRESULT STDMETHODCALLTYPE GetLastWriteTime(OUT UINT64 *lastWriteTime); + + private: + QByteArray m_fontData; + ULONG m_referenceCount; + }; + + HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::QueryInterface(REFIID iid, void **object) + { + if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileStream)) { + *object = this; + AddRef(); + return S_OK; + } else { + *object = NULL; + return E_NOINTERFACE; + } + } + + ULONG STDMETHODCALLTYPE DirectWriteFontFileStream::AddRef() + { + return InterlockedIncrement(&m_referenceCount); + } + + ULONG STDMETHODCALLTYPE DirectWriteFontFileStream::Release() + { + ULONG newCount = InterlockedDecrement(&m_referenceCount); + if (newCount == 0) + delete this; + return newCount; + } + + HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::ReadFileFragment( + const void **fragmentStart, + UINT64 fileOffset, + UINT64 fragmentSize, + OUT void **fragmentContext) + { + *fragmentContext = NULL; + if (fragmentSize + fileOffset <= m_fontData.size()) { + *fragmentStart = m_fontData.data() + fileOffset; + return S_OK; + } else { + *fragmentStart = NULL; + return E_FAIL; + } + } + + void STDMETHODCALLTYPE DirectWriteFontFileStream::ReleaseFileFragment(void *) + { + } + + HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::GetFileSize(UINT64 *fileSize) + { + *fileSize = m_fontData.size(); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::GetLastWriteTime(UINT64 *lastWriteTime) + { + *lastWriteTime = 0; + return E_NOTIMPL; + } + + class DirectWriteFontFileLoader: public IDWriteFontFileLoader + { + public: + DirectWriteFontFileLoader() : m_referenceCount(0) {} + + ~DirectWriteFontFileLoader() + { + } + + inline void addKey(const void *key, const QByteArray &fontData) + { + Q_ASSERT(!m_fontDatas.contains(key)); + m_fontDatas.insert(key, fontData); + } + + inline void removeKey(const void *key) + { + m_fontDatas.remove(key); + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **object); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + + HRESULT STDMETHODCALLTYPE CreateStreamFromKey(void const *fontFileReferenceKey, + UINT32 fontFileReferenceKeySize, + OUT IDWriteFontFileStream **fontFileStream); + + private: + ULONG m_referenceCount; + QHash m_fontDatas; + }; + + HRESULT STDMETHODCALLTYPE DirectWriteFontFileLoader::QueryInterface(const IID &iid, + void **object) + { + if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileLoader)) { + *object = this; + AddRef(); + return S_OK; + } else { + *object = NULL; + return E_NOINTERFACE; + } + } + + ULONG STDMETHODCALLTYPE DirectWriteFontFileLoader::AddRef() + { + return InterlockedIncrement(&m_referenceCount); + } + + ULONG STDMETHODCALLTYPE DirectWriteFontFileLoader::Release() + { + ULONG newCount = InterlockedDecrement(&m_referenceCount); + if (newCount == 0) + delete this; + return newCount; + } + + HRESULT STDMETHODCALLTYPE DirectWriteFontFileLoader::CreateStreamFromKey( + void const *fontFileReferenceKey, + UINT32 fontFileReferenceKeySize, + IDWriteFontFileStream **fontFileStream) + { + Q_UNUSED(fontFileReferenceKeySize); + + if (fontFileReferenceKeySize != sizeof(const void *)) { + qWarning("DirectWriteFontFileLoader::CreateStreamFromKey: Wrong key size"); + return E_FAIL; + } + + const void *key = *reinterpret_cast(fontFileReferenceKey); + *fontFileStream = NULL; + if (!m_fontDatas.contains(key)) + return E_FAIL; + + QByteArray fontData = m_fontDatas.value(key); + DirectWriteFontFileStream *stream = new DirectWriteFontFileStream(fontData); + stream->AddRef(); + *fontFileStream = stream; + + return S_OK; + } + + class CustomFontFileLoader + { + public: + CustomFontFileLoader() : m_directWriteFactory(0), m_directWriteFontFileLoader(0) + { + HRESULT hres = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory), + reinterpret_cast(&m_directWriteFactory)); + if (FAILED(hres)) { + qErrnoWarning(hres, "CustomFontFileLoader::CustomFontFileLoader: " + "DWriteCreateFactory failed."); + } else { + m_directWriteFontFileLoader = new DirectWriteFontFileLoader(); + m_directWriteFactory->RegisterFontFileLoader(m_directWriteFontFileLoader); + } + } + + ~CustomFontFileLoader() + { + if (m_directWriteFactory != 0 && m_directWriteFontFileLoader != 0) + m_directWriteFactory->UnregisterFontFileLoader(m_directWriteFontFileLoader); + + if (m_directWriteFactory != 0) + m_directWriteFactory->Release(); + } + + void addKey(const void *key, const QByteArray &fontData) + { + if (m_directWriteFontFileLoader != 0) + m_directWriteFontFileLoader->addKey(key, fontData); + } + + void removeKey(const void *key) + { + if (m_directWriteFontFileLoader != 0) + m_directWriteFontFileLoader->removeKey(key); + } + + IDWriteFontFileLoader *loader() const + { + return m_directWriteFontFileLoader; + } + + private: + IDWriteFactory *m_directWriteFactory; + DirectWriteFontFileLoader *m_directWriteFontFileLoader; + }; + +#endif + +} // Anonymous namespace + + +// From qfontdatabase_win.cpp +extern QFontEngine *qt_load_font_engine_win(const QFontDef &request); +// From qfontdatabase.cpp +extern QFont::Weight weightFromInteger(int weight); + +void QRawFontPrivate::platformCleanUp() +{ + if (fontHandle != NULL) { + if (ptrRemoveFontMemResourceEx == NULL) { + void *func = QSystemLibrary::resolve(QLatin1String("gdi32"), "RemoveFontMemResourceEx"); + ptrRemoveFontMemResourceEx = + reinterpret_cast(func); + } + + if (ptrRemoveFontMemResourceEx == NULL) { + qWarning("QRawFont::platformCleanUp: Can't find RemoveFontMemResourceEx in gdi32"); + fontHandle = NULL; + } else { + ptrRemoveFontMemResourceEx(fontHandle); + fontHandle = NULL; + } + } +} + +void QRawFontPrivate::platformLoadFromData(const QByteArray &_fontData, + int pixelSize, + QFont::HintingPreference hintingPreference) +{ + QByteArray fontData(_fontData); + EmbeddedFont font(fontData); + +#if !defined(QT_NO_DIRECTWRITE) + if (hintingPreference == QFont::PreferDefaultHinting + || hintingPreference == QFont::PreferFullHinting) +#endif + { + GUID guid; + CoCreateGuid(&guid); + + uniqueFamilyName = QString::fromLatin1("f") + + QString::number(guid.Data1, 36) + QLatin1Char('-') + + QString::number(guid.Data2, 36) + QLatin1Char('-') + + QString::number(guid.Data3, 36) + QLatin1Char('-') + + QString::number(*reinterpret_cast(guid.Data4), 36); + + QString actualFontName = font.changeFamilyName(uniqueFamilyName); + if (actualFontName.isEmpty()) { + qWarning("QRawFont::platformLoadFromData: Can't change family name of font"); + return; + } + + if (ptrAddFontMemResourceEx == NULL || ptrRemoveFontMemResourceEx == NULL) { + void *func = QSystemLibrary::resolve(QLatin1String("gdi32"), "RemoveFontMemResourceEx"); + ptrRemoveFontMemResourceEx = + reinterpret_cast(func); + + func = QSystemLibrary::resolve(QLatin1String("gdi32"), "AddFontMemResourceEx"); + ptrAddFontMemResourceEx = + reinterpret_cast(func); + } + + Q_ASSERT(fontHandle == NULL); + if (ptrAddFontMemResourceEx != NULL && ptrRemoveFontMemResourceEx != NULL) { + DWORD count = 0; + fontData = font.data(); + fontHandle = ptrAddFontMemResourceEx(fontData.data(), fontData.size(), 0, &count); + + if (count == 0 && fontHandle != NULL) { + ptrRemoveFontMemResourceEx(fontHandle); + fontHandle = NULL; + } + } + + if (fontHandle == NULL) { + qWarning("QRawFont::platformLoadFromData: AddFontMemResourceEx failed"); + } else { + QFontDef request; + request.family = uniqueFamilyName; + request.pixelSize = pixelSize; + request.styleStrategy = QFont::NoFontMerging | QFont::PreferMatch; + request.hintingPreference = hintingPreference; + + fontEngine = qt_load_font_engine_win(request); + if (request.family != fontEngine->fontDef.family) { + qWarning("QRawFont::platformLoadFromData: Failed to load font. " + "Got fallback instead: %s", qPrintable(fontEngine->fontDef.family)); + if (fontEngine->cache_count == 0 && fontEngine->ref == 0) + delete fontEngine; + fontEngine = 0; + } else { + Q_ASSERT(fontEngine->cache_count == 0 && fontEngine->ref == 0); + + // Override the generated font name + fontEngine->fontDef.family = actualFontName; + fontEngine->ref.ref(); + } + } + } +#if !defined(QT_NO_DIRECTWRITE) + else { + CustomFontFileLoader fontFileLoader; + fontFileLoader.addKey(this, fontData); + + IDWriteFactory *factory = NULL; + HRESULT hres = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory), + reinterpret_cast(&factory)); + if (FAILED(hres)) { + qErrnoWarning(hres, "QRawFont::platformLoadFromData: DWriteCreateFactory failed"); + return; + } + + IDWriteFontFile *fontFile = NULL; + void *key = this; + + hres = factory->CreateCustomFontFileReference(&key, sizeof(void *), + fontFileLoader.loader(), &fontFile); + if (FAILED(hres)) { + qErrnoWarning(hres, "QRawFont::platformLoadFromData: " + "CreateCustomFontFileReference failed"); + factory->Release(); + return; + } + + BOOL isSupportedFontType; + DWRITE_FONT_FILE_TYPE fontFileType; + DWRITE_FONT_FACE_TYPE fontFaceType; + UINT32 numberOfFaces; + fontFile->Analyze(&isSupportedFontType, &fontFileType, &fontFaceType, &numberOfFaces); + if (!isSupportedFontType) { + fontFile->Release(); + factory->Release(); + return; + } + + IDWriteFontFace *directWriteFontFace = NULL; + hres = factory->CreateFontFace(fontFaceType, 1, &fontFile, 0, DWRITE_FONT_SIMULATIONS_NONE, + &directWriteFontFace); + if (FAILED(hres)) { + qErrnoWarning(hres, "QRawFont::platformLoadFromData: CreateFontFace failed"); + fontFile->Release(); + factory->Release(); + return; + } + + fontFile->Release(); + + fontEngine = new QFontEngineDirectWrite(factory, directWriteFontFace, pixelSize); + + // Get font family from font data + fontEngine->fontDef.family = font.familyName(); + fontEngine->ref.ref(); + + directWriteFontFace->Release(); + factory->Release(); + } +#endif + + // Get style and weight info + if (fontEngine != 0) { + TableDirectory *os2TableEntry = font.tableDirectoryEntry("OS/2"); + if (os2TableEntry != 0) { + const OS2Table *os2Table = + reinterpret_cast(fontData.constData() + + os2TableEntry->offset); + + bool italic = os2Table->selection & 1; + bool oblique = os2Table->selection & 128; + + if (italic) + fontEngine->fontDef.style = QFont::StyleItalic; + else if (oblique) + fontEngine->fontDef.style = QFont::StyleOblique; + else + fontEngine->fontDef.style = QFont::StyleNormal; + + fontEngine->fontDef.weight = weightFromInteger(os2Table->weightClass); + } + } +} + +void QRawFontPrivate::platformSetPixelSize(int pixelSize) +{ + if (fontEngine == NULL) + return; + + QFontEngine *oldFontEngine = fontEngine; + +#if !defined(QT_NO_DIRECTWRITE) + if (fontEngine->type() == QFontEngine::Win) +#endif + + { + QFontDef request = fontEngine->fontDef; + QString actualFontName = request.family; + if (!uniqueFamilyName.isEmpty()) + request.family = uniqueFamilyName; + request.pixelSize = pixelSize; + + fontEngine = qt_load_font_engine_win(request); + if (fontEngine != NULL) { + fontEngine->fontDef.family = actualFontName; + fontEngine->ref.ref(); + } + } + +#if !defined(QT_NO_DIRECTWRITE) + else { + QFontEngineDirectWrite *dWriteFE = static_cast(fontEngine); + fontEngine = new QFontEngineDirectWrite(dWriteFE->m_directWriteFactory, + dWriteFE->m_directWriteFontFace, + pixelSize); + + fontEngine->fontDef = dWriteFE->fontDef; + fontEngine->fontDef.pixelSize = pixelSize; + fontEngine->ref.ref(); + } +#endif + + Q_ASSERT(fontEngine != oldFontEngine); + oldFontEngine->ref.deref(); + if (oldFontEngine->cache_count == 0 && oldFontEngine->ref == 0) + delete oldFontEngine; +} + +QT_END_NAMESPACE + +#endif // QT_NO_RAWFONT diff --git a/src/gui/text/qstatictext.cpp b/src/gui/text/qstatictext.cpp new file mode 100644 index 0000000000..1cfb4b61f9 --- /dev/null +++ b/src/gui/text/qstatictext.cpp @@ -0,0 +1,733 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qstatictext.h" +#include "qstatictext_p.h" +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QStaticText + \brief The QStaticText class enables optimized drawing of text when the text and its layout + is updated rarely. + \since 4.7 + + \ingroup multimedia + \ingroup text + \mainclass + + QStaticText provides a way to cache layout data for a block of text so that it can be drawn + more efficiently than by using QPainter::drawText() in which the layout information is + recalculated with every call. + + The class primarily provides an optimization for cases where the text, its font and the + transformations on the painter are static over several paint events. If the text or its layout + is changed for every iteration, QPainter::drawText() is the more efficient alternative, since + the static text's layout would have to be recalculated to take the new state into consideration. + + Translating the painter will not cause the layout of the text to be recalculated, but will cause + a very small performance impact on drawStaticText(). Altering any other parts of the painter's + transformation or the painter's font will cause the layout of the static text to be + recalculated. This should be avoided as often as possible to maximize the performance + benefit of using QStaticText. + + In addition, only affine transformations are supported by drawStaticText(). Calling + drawStaticText() on a projected painter will perform slightly worse than using the regular + drawText() call, so this should be avoided. + + \code + class MyWidget: public QWidget + { + public: + MyWidget(QWidget *parent = 0) : QWidget(parent), m_staticText("This is static text") + + protected: + void paintEvent(QPaintEvent *) + { + QPainter painter(this); + painter.drawStaticText(0, 0, m_staticText); + } + + private: + QStaticText m_staticText; + }; + \endcode + + The QStaticText class can be used to mimic the behavior of QPainter::drawText() to a specific + point with no boundaries, and also when QPainter::drawText() is called with a bounding + rectangle. + + If a bounding rectangle is not required, create a QStaticText object without setting a preferred + text width. The text will then occupy a single line. + + If you set a text width on the QStaticText object, this will bound the text. The text will + be formatted so that no line exceeds the given width. The text width set for QStaticText will + not automatically be used for clipping. To achieve clipping in addition to line breaks, use + QPainter::setClipRect(). The position of the text is decided by the argument passed to + QPainter::drawStaticText() and can change from call to call with a minimal impact on + performance. + + For extra convenience, it is possible to apply formatting to the text using the HTML subset + supported by QTextDocument. QStaticText will attempt to guess the format of the input text using + Qt::mightBeRichText(), and interpret it as rich text if this function returns true. To force + QStaticText to display its contents as either plain text or rich text, use the function + QStaticText::setTextFormat() and pass in, respectively, Qt::PlainText and Qt::RichText. + + QStaticText can only represent text, so only HTML tags which alter the layout or appearance of + the text will be respected. Adding an image to the input HTML, for instance, will cause the + image to be included as part of the layout, affecting the positions of the text glyphs, but it + will not be displayed. The result will be an empty area the size of the image in the output. + Similarly, using tables will cause the text to be laid out in table format, but the borders + will not be drawn. + + If it's the first time the static text is drawn, or if the static text, or the painter's font + has been altered since the last time it was drawn, the text's layout has to be + recalculated. On some paint engines, changing the matrix of the painter will also cause the + layout to be recalculated. In particular, this will happen for any engine except for the + OpenGL2 paint engine. Recalculating the layout will impose an overhead on the + QPainter::drawStaticText() call where it occurs. To avoid this overhead in the paint event, you + can call prepare() ahead of time to ensure that the layout is calculated. + + \sa QPainter::drawText(), QPainter::drawStaticText(), QTextLayout, QTextDocument +*/ + +/*! + \enum QStaticText::PerformanceHint + + This enum the different performance hints that can be set on the QStaticText. These hints + can be used to indicate that the QStaticText should use additional caches, if possible, + to improve performance at the expense of memory. In particular, setting the performance hint + AggressiveCaching on the QStaticText will improve performance when using the OpenGL graphics + system or when drawing to a QGLWidget. + + \value ModerateCaching Do basic caching for high performance at a low memory cost. + \value AggressiveCaching Use additional caching when available. This may improve performance + at a higher memory cost. +*/ + +/*! + Constructs an empty QStaticText +*/ +QStaticText::QStaticText() + : data(new QStaticTextPrivate) +{ +} + +/*! + Constructs a QStaticText object with the given \a text. +*/ +QStaticText::QStaticText(const QString &text) + : data(new QStaticTextPrivate) +{ + data->text = text; + data->invalidate(); +} + +/*! + Constructs a QStaticText object which is a copy of \a other. +*/ +QStaticText::QStaticText(const QStaticText &other) +{ + data = other.data; +} + +/*! + Destroys the QStaticText. +*/ +QStaticText::~QStaticText() +{ + Q_ASSERT(!data || data->ref >= 1); +} + +/*! + \internal +*/ +void QStaticText::detach() +{ + if (data->ref != 1) + data.detach(); +} + +/*! + Prepares the QStaticText object for being painted with the given \a matrix and the given \a font + to avoid overhead when the actual drawStaticText() call is made. + + When drawStaticText() is called, the layout of the QStaticText will be recalculated if any part + of the QStaticText object has changed since the last time it was drawn. It will also be + recalculated if the painter's font is not the same as when the QStaticText was last drawn, or, + on any other paint engine than the OpenGL2 engine, if the painter's matrix has been altered + since the static text was last drawn. + + To avoid the overhead of creating the layout the first time you draw the QStaticText after + making changes, you can use the prepare() function and pass in the \a matrix and \a font you + expect to use when drawing the text. + + \sa QPainter::setFont(), QPainter::setMatrix() +*/ +void QStaticText::prepare(const QTransform &matrix, const QFont &font) +{ + data->matrix = matrix; + data->font = font; + data->init(); +} + + +/*! + Assigns \a other to this QStaticText. +*/ +QStaticText &QStaticText::operator=(const QStaticText &other) +{ + data = other.data; + return *this; +} + +/*! + Compares \a other to this QStaticText. Returns true if the texts, fonts and text widths + are equal. +*/ +bool QStaticText::operator==(const QStaticText &other) const +{ + return (data == other.data + || (data->text == other.data->text + && data->font == other.data->font + && data->textWidth == other.data->textWidth)); +} + +/*! + Compares \a other to this QStaticText. Returns true if the texts, fonts or maximum sizes + are different. +*/ +bool QStaticText::operator!=(const QStaticText &other) const +{ + return !(*this == other); +} + +/*! + Sets the text of the QStaticText to \a text. + + \note This function will cause the layout of the text to require recalculation. + + \sa text() +*/ +void QStaticText::setText(const QString &text) +{ + detach(); + data->text = text; + data->invalidate(); +} + +/*! + Sets the text format of the QStaticText to \a textFormat. If \a textFormat is set to + Qt::AutoText (the default), the format of the text will try to be determined using the + function Qt::mightBeRichText(). If the text format is Qt::PlainText, then the text will be + displayed as is, whereas it will be interpreted as HTML if the format is Qt::RichText. HTML tags + that alter the font of the text, its color, or its layout are supported by QStaticText. + + \note This function will cause the layout of the text to require recalculation. + + \sa textFormat(), setText(), text() +*/ +void QStaticText::setTextFormat(Qt::TextFormat textFormat) +{ + detach(); + data->textFormat = textFormat; + data->invalidate(); +} + +/*! + Returns the text format of the QStaticText. + + \sa setTextFormat(), setText(), text() +*/ +Qt::TextFormat QStaticText::textFormat() const +{ + return Qt::TextFormat(data->textFormat); +} + +/*! + Returns the text of the QStaticText. + + \sa setText() +*/ +QString QStaticText::text() const +{ + return data->text; +} + +/*! + Sets the performance hint of the QStaticText according to the \a + performanceHint provided. The \a performanceHint is used to + customize how much caching is done internally to improve + performance. + + The default is QStaticText::ModerateCaching. + + \note This function will cause the layout of the text to require recalculation. + + \sa performanceHint() +*/ +void QStaticText::setPerformanceHint(PerformanceHint performanceHint) +{ + if ((performanceHint == ModerateCaching && !data->useBackendOptimizations) + || (performanceHint == AggressiveCaching && data->useBackendOptimizations)) { + return; + } + detach(); + data->useBackendOptimizations = (performanceHint == AggressiveCaching); + data->invalidate(); +} + +/*! + Returns which performance hint is set for the QStaticText. + + \sa setPerformanceHint() +*/ +QStaticText::PerformanceHint QStaticText::performanceHint() const +{ + return data->useBackendOptimizations ? AggressiveCaching : ModerateCaching; +} + +/*! + Sets the text option structure that controls the layout process to the given \a textOption. + + \sa textOption() +*/ +void QStaticText::setTextOption(const QTextOption &textOption) +{ + detach(); + data->textOption = textOption; + data->invalidate(); +} + +/*! + Returns the current text option used to control the layout process. +*/ +QTextOption QStaticText::textOption() const +{ + return data->textOption; +} + +/*! + Sets the preferred width for this QStaticText. If the text is wider than the specified width, + it will be broken into multiple lines and grow vertically. If the text cannot be split into + multiple lines, it will be larger than the specified \a textWidth. + + Setting the preferred text width to a negative number will cause the text to be unbounded. + + Use size() to get the actual size of the text. + + \note This function will cause the layout of the text to require recalculation. + + \sa textWidth(), size() +*/ +void QStaticText::setTextWidth(qreal textWidth) +{ + detach(); + data->textWidth = textWidth; + data->invalidate(); +} + +/*! + Returns the preferred width for this QStaticText. + + \sa setTextWidth() +*/ +qreal QStaticText::textWidth() const +{ + return data->textWidth; +} + +/*! + Returns the size of the bounding rect for this QStaticText. + + \sa textWidth() +*/ +QSizeF QStaticText::size() const +{ + if (data->needsRelayout) + data->init(); + return data->actualSize; +} + +QStaticTextPrivate::QStaticTextPrivate() + : textWidth(-1.0), items(0), itemCount(0), glyphPool(0), positionPool(0), charPool(0), + needsRelayout(true), useBackendOptimizations(false), textFormat(Qt::AutoText), + untransformedCoordinates(false) +{ +} + +QStaticTextPrivate::QStaticTextPrivate(const QStaticTextPrivate &other) + : text(other.text), font(other.font), textWidth(other.textWidth), matrix(other.matrix), + items(0), itemCount(0), glyphPool(0), positionPool(0), charPool(0), textOption(other.textOption), + needsRelayout(true), useBackendOptimizations(other.useBackendOptimizations), + textFormat(other.textFormat), untransformedCoordinates(other.untransformedCoordinates) +{ +} + +QStaticTextPrivate::~QStaticTextPrivate() +{ + delete[] items; + delete[] glyphPool; + delete[] positionPool; + delete[] charPool; +} + +QStaticTextPrivate *QStaticTextPrivate::get(const QStaticText *q) +{ + return q->data.data(); +} + +namespace { + + class DrawTextItemRecorder: public QPaintEngine + { + public: + DrawTextItemRecorder(bool untransformedCoordinates, bool useBackendOptimizations) + : m_dirtyPen(false), m_useBackendOptimizations(useBackendOptimizations), + m_untransformedCoordinates(untransformedCoordinates) + { + } + + virtual void updateState(const QPaintEngineState &newState) + { + if (newState.state() & QPaintEngine::DirtyPen) + m_dirtyPen = true; + } + + virtual void drawTextItem(const QPointF &position, const QTextItem &textItem) + { + const QTextItemInt &ti = static_cast(textItem); + + QStaticTextItem currentItem; + currentItem.setFontEngine(ti.fontEngine); + currentItem.font = ti.font(); + currentItem.charOffset = m_chars.size(); + currentItem.numChars = ti.num_chars; + currentItem.glyphOffset = m_glyphs.size(); // Store offset into glyph pool + currentItem.positionOffset = m_glyphs.size(); // Offset into position pool + currentItem.useBackendOptimizations = m_useBackendOptimizations; + if (m_dirtyPen) + currentItem.color = state->pen().color(); + + QTransform matrix = m_untransformedCoordinates ? QTransform() : state->transform(); + matrix.translate(position.x(), position.y()); + + QVarLengthArray glyphs; + QVarLengthArray positions; + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + + int size = glyphs.size(); + Q_ASSERT(size == positions.size()); + currentItem.numGlyphs = size; + + m_glyphs.resize(m_glyphs.size() + size); + m_positions.resize(m_glyphs.size()); + m_chars.resize(m_chars.size() + ti.num_chars); + + glyph_t *glyphsDestination = m_glyphs.data() + currentItem.glyphOffset; + memcpy(glyphsDestination, glyphs.constData(), sizeof(glyph_t) * currentItem.numGlyphs); + + QFixedPoint *positionsDestination = m_positions.data() + currentItem.positionOffset; + memcpy(positionsDestination, positions.constData(), sizeof(QFixedPoint) * currentItem.numGlyphs); + + QChar *charsDestination = m_chars.data() + currentItem.charOffset; + memcpy(charsDestination, ti.chars, sizeof(QChar) * currentItem.numChars); + + m_items.append(currentItem); + } + + virtual void drawPolygon(const QPointF *, int , PolygonDrawMode ) + { + /* intentionally empty */ + } + + virtual bool begin(QPaintDevice *) { return true; } + virtual bool end() { return true; } + virtual void drawPixmap(const QRectF &, const QPixmap &, const QRectF &) {} + virtual Type type() const + { + return User; + } + + QVector items() const + { + return m_items; + } + + QVector positions() const + { + return m_positions; + } + + QVector glyphs() const + { + return m_glyphs; + } + + QVector chars() const + { + return m_chars; + } + + private: + QVector m_items; + QVector m_positions; + QVector m_glyphs; + QVector m_chars; + + bool m_dirtyPen; + bool m_useBackendOptimizations; + bool m_untransformedCoordinates; + }; + + class DrawTextItemDevice: public QPaintDevice + { + public: + DrawTextItemDevice(bool untransformedCoordinates, bool useBackendOptimizations) + { + m_paintEngine = new DrawTextItemRecorder(untransformedCoordinates, + useBackendOptimizations); + } + + ~DrawTextItemDevice() + { + delete m_paintEngine; + } + + int metric(PaintDeviceMetric m) const + { + int val; + switch (m) { + case PdmWidth: + case PdmHeight: + case PdmWidthMM: + case PdmHeightMM: + val = 0; + break; + case PdmDpiX: + case PdmPhysicalDpiX: + val = qt_defaultDpiX(); + break; + case PdmDpiY: + case PdmPhysicalDpiY: + val = qt_defaultDpiY(); + break; + case PdmNumColors: + val = 16777216; + break; + case PdmDepth: + val = 24; + break; + default: + val = 0; + qWarning("DrawTextItemDevice::metric: Invalid metric command"); + } + return val; + } + + virtual QPaintEngine *paintEngine() const + { + return m_paintEngine; + } + + QVector glyphs() const + { + return m_paintEngine->glyphs(); + } + + QVector positions() const + { + return m_paintEngine->positions(); + } + + QVector items() const + { + return m_paintEngine->items(); + } + + QVector chars() const + { + return m_paintEngine->chars(); + } + + private: + DrawTextItemRecorder *m_paintEngine; + }; +} + +void QStaticTextPrivate::paintText(const QPointF &topLeftPosition, QPainter *p) +{ + bool preferRichText = textFormat == Qt::RichText + || (textFormat == Qt::AutoText && Qt::mightBeRichText(text)); + + if (!preferRichText) { + QTextLayout textLayout; + textLayout.setText(text); + textLayout.setFont(font); + textLayout.setTextOption(textOption); + + qreal leading = QFontMetricsF(font).leading(); + qreal height = -leading; + + textLayout.beginLayout(); + while (1) { + QTextLine line = textLayout.createLine(); + if (!line.isValid()) + break; + + if (textWidth >= 0.0) + line.setLineWidth(textWidth); + height += leading; + line.setPosition(QPointF(0.0, height)); + height += line.height(); + } + textLayout.endLayout(); + + actualSize = textLayout.boundingRect().size(); + textLayout.draw(p, topLeftPosition); + } else { + QTextDocument document; +#ifndef QT_NO_CSSPARSER + QColor color = p->pen().color(); + document.setDefaultStyleSheet(QString::fromLatin1("body { color: #%1%2%3 }") + .arg(QString::number(color.red(), 16), 2, QLatin1Char('0')) + .arg(QString::number(color.green(), 16), 2, QLatin1Char('0')) + .arg(QString::number(color.blue(), 16), 2, QLatin1Char('0'))); +#endif + document.setDefaultFont(font); + document.setDocumentMargin(0.0); +#ifndef QT_NO_TEXTHTMLPARSER + document.setHtml(text); +#else + document.setPlainText(text); +#endif + if (textWidth >= 0.0) + document.setTextWidth(textWidth); + else + document.adjustSize(); + document.setDefaultTextOption(textOption); + + p->save(); + p->translate(topLeftPosition); + QAbstractTextDocumentLayout::PaintContext ctx; + ctx.palette.setColor(QPalette::Text, p->pen().color()); + document.documentLayout()->draw(p, ctx); + p->restore(); + + if (textWidth >= 0.0) + document.adjustSize(); // Find optimal size + + actualSize = document.size(); + } +} + +void QStaticTextPrivate::init() +{ + delete[] items; + delete[] glyphPool; + delete[] positionPool; + delete[] charPool; + + position = QPointF(0, 0); + + DrawTextItemDevice device(untransformedCoordinates, useBackendOptimizations); + { + QPainter painter(&device); + painter.setFont(font); + painter.setTransform(matrix); + + paintText(QPointF(0, 0), &painter); + } + + QVector deviceItems = device.items(); + QVector positions = device.positions(); + QVector glyphs = device.glyphs(); + QVector chars = device.chars(); + + itemCount = deviceItems.size(); + items = new QStaticTextItem[itemCount]; + + glyphPool = new glyph_t[glyphs.size()]; + memcpy(glyphPool, glyphs.constData(), glyphs.size() * sizeof(glyph_t)); + + positionPool = new QFixedPoint[positions.size()]; + memcpy(positionPool, positions.constData(), positions.size() * sizeof(QFixedPoint)); + + charPool = new QChar[chars.size()]; + memcpy(charPool, chars.constData(), chars.size() * sizeof(QChar)); + + for (int i=0; iref.deref()) + delete m_userData; + if (!m_fontEngine->ref.deref()) + delete m_fontEngine; +} + +void QStaticTextItem::setFontEngine(QFontEngine *fe) +{ + if (m_fontEngine != 0) { + if (!m_fontEngine->ref.deref()) + delete m_fontEngine; + } + + m_fontEngine = fe; + if (m_fontEngine != 0) + m_fontEngine->ref.ref(); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qstatictext.h b/src/gui/text/qstatictext.h new file mode 100644 index 0000000000..dc3e913ae9 --- /dev/null +++ b/src/gui/text/qstatictext.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTATICTEXT_H +#define QSTATICTEXT_H + +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QStaticTextPrivate; +class Q_GUI_EXPORT QStaticText +{ +public: + enum PerformanceHint { + ModerateCaching, + AggressiveCaching + }; + + QStaticText(); + QStaticText(const QString &text); + QStaticText(const QStaticText &other); + ~QStaticText(); + + void setText(const QString &text); + QString text() const; + + void setTextFormat(Qt::TextFormat textFormat); + Qt::TextFormat textFormat() const; + + void setTextWidth(qreal textWidth); + qreal textWidth() const; + + void setTextOption(const QTextOption &textOption); + QTextOption textOption() const; + + QSizeF size() const; + + void prepare(const QTransform &matrix = QTransform(), const QFont &font = QFont()); + + void setPerformanceHint(PerformanceHint performanceHint); + PerformanceHint performanceHint() const; + + QStaticText &operator=(const QStaticText &); + bool operator==(const QStaticText &) const; + bool operator!=(const QStaticText &) const; + +private: + void detach(); + + QExplicitlySharedDataPointer data; + friend class QStaticTextPrivate; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QStaticText) + +QT_END_HEADER + +#endif // QSTATICTEXT_H diff --git a/src/gui/text/qstatictext_p.h b/src/gui/text/qstatictext_p.h new file mode 100644 index 0000000000..af11a97c1b --- /dev/null +++ b/src/gui/text/qstatictext_p.h @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSTATICTEXT_P_H +#define QSTATICTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of internal files. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#include "qstatictext.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QStaticTextUserData +{ +public: + enum Type { + NoUserData, + OpenGLUserData + }; + + QStaticTextUserData(Type t) : type(t) { ref = 0; } + virtual ~QStaticTextUserData() {} + + QAtomicInt ref; + Type type; +}; + +class Q_GUI_EXPORT QStaticTextItem +{ +public: + QStaticTextItem() : chars(0), numChars(0), useBackendOptimizations(false), + userDataNeedsUpdate(0), m_fontEngine(0), m_userData(0) {} + + QStaticTextItem(const QStaticTextItem &other) + { + operator=(other); + } + + void operator=(const QStaticTextItem &other) + { + glyphPositions = other.glyphPositions; + glyphs = other.glyphs; + chars = other.chars; + numGlyphs = other.numGlyphs; + numChars = other.numChars; + font = other.font; + color = other.color; + useBackendOptimizations = other.useBackendOptimizations; + userDataNeedsUpdate = other.userDataNeedsUpdate; + + m_fontEngine = 0; + m_userData = 0; + setUserData(other.userData()); + setFontEngine(other.fontEngine()); + } + + ~QStaticTextItem(); + + void setUserData(QStaticTextUserData *newUserData) + { + if (m_userData == newUserData) + return; + + if (m_userData != 0 && !m_userData->ref.deref()) + delete m_userData; + + m_userData = newUserData; + if (m_userData != 0) + m_userData->ref.ref(); + } + QStaticTextUserData *userData() const { return m_userData; } + + void setFontEngine(QFontEngine *fe); + QFontEngine *fontEngine() const { return m_fontEngine; } + + union { + QFixedPoint *glyphPositions; // 8 bytes per glyph + int positionOffset; + }; + union { + glyph_t *glyphs; // 4 bytes per glyph + int glyphOffset; + }; + union { + QChar *chars; // 2 bytes per glyph + int charOffset; + }; + // ================= + // 14 bytes per glyph + + // 12 bytes for pointers + int numGlyphs; // 4 bytes per item + int numChars; // 4 bytes per item + QFont font; // 8 bytes per item + QColor color; // 10 bytes per item + char useBackendOptimizations : 1; // 1 byte per item + char userDataNeedsUpdate : 1; // + // ================ + // 51 bytes per item + +private: // Needs special handling in setters, so private to avoid abuse + QFontEngine *m_fontEngine; // 4 bytes per item + QStaticTextUserData *m_userData; // 8 bytes per item + +}; + +class QStaticText; +class Q_AUTOTEST_EXPORT QStaticTextPrivate +{ +public: + QStaticTextPrivate(); + QStaticTextPrivate(const QStaticTextPrivate &other); + ~QStaticTextPrivate(); + + void init(); + void paintText(const QPointF &pos, QPainter *p); + + void invalidate() + { + needsRelayout = true; + } + + QAtomicInt ref; // 4 bytes per text + + QString text; // 4 bytes per text + QFont font; // 8 bytes per text + qreal textWidth; // 8 bytes per text + QSizeF actualSize; // 16 bytes per text + QPointF position; // 16 bytes per text + + QTransform matrix; // 80 bytes per text + QStaticTextItem *items; // 4 bytes per text + int itemCount; // 4 bytes per text + + glyph_t *glyphPool; // 4 bytes per text + QFixedPoint *positionPool; // 4 bytes per text + QChar *charPool; // 4 bytes per text + + QTextOption textOption; // 28 bytes per text + + unsigned char needsRelayout : 1; // 1 byte per text + unsigned char useBackendOptimizations : 1; + unsigned char textFormat : 2; + unsigned char untransformedCoordinates : 1; + // ================ + // 195 bytes per text + + static QStaticTextPrivate *get(const QStaticText *q); +}; + +QT_END_NAMESPACE + +#endif // QSTATICTEXT_P_H diff --git a/src/gui/text/qsyntaxhighlighter.cpp b/src/gui/text/qsyntaxhighlighter.cpp new file mode 100644 index 0000000000..6a3355b90d --- /dev/null +++ b/src/gui/text/qsyntaxhighlighter.cpp @@ -0,0 +1,665 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsyntaxhighlighter.h" + +#ifndef QT_NO_SYNTAXHIGHLIGHTER +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QSyntaxHighlighterPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QSyntaxHighlighter) +public: + inline QSyntaxHighlighterPrivate() + : rehighlightPending(false), inReformatBlocks(false) + {} + + QPointer doc; + + void _q_reformatBlocks(int from, int charsRemoved, int charsAdded); + void reformatBlocks(int from, int charsRemoved, int charsAdded); + void reformatBlock(const QTextBlock &block); + + inline void rehighlight(QTextCursor &cursor, QTextCursor::MoveOperation operation) { + inReformatBlocks = true; + cursor.beginEditBlock(); + int from = cursor.position(); + cursor.movePosition(operation); + reformatBlocks(from, 0, cursor.position() - from); + cursor.endEditBlock(); + inReformatBlocks = false; + } + + inline void _q_delayedRehighlight() { + if (!rehighlightPending) + return; + rehighlightPending = false; + q_func()->rehighlight(); + } + + void applyFormatChanges(); + QVector formatChanges; + QTextBlock currentBlock; + bool rehighlightPending; + bool inReformatBlocks; +}; + +void QSyntaxHighlighterPrivate::applyFormatChanges() +{ + bool formatsChanged = false; + + QTextLayout *layout = currentBlock.layout(); + + QList ranges = layout->additionalFormats(); + + const int preeditAreaStart = layout->preeditAreaPosition(); + const int preeditAreaLength = layout->preeditAreaText().length(); + + if (preeditAreaLength != 0) { + QList::Iterator it = ranges.begin(); + while (it != ranges.end()) { + if (it->start >= preeditAreaStart + && it->start + it->length <= preeditAreaStart + preeditAreaLength) { + ++it; + } else { + it = ranges.erase(it); + formatsChanged = true; + } + } + } else if (!ranges.isEmpty()) { + ranges.clear(); + formatsChanged = true; + } + + QTextCharFormat emptyFormat; + + QTextLayout::FormatRange r; + r.start = -1; + + int i = 0; + while (i < formatChanges.count()) { + + while (i < formatChanges.count() && formatChanges.at(i) == emptyFormat) + ++i; + + if (i >= formatChanges.count()) + break; + + r.start = i; + r.format = formatChanges.at(i); + + while (i < formatChanges.count() && formatChanges.at(i) == r.format) + ++i; + + if (i >= formatChanges.count()) + break; + + r.length = i - r.start; + + if (preeditAreaLength != 0) { + if (r.start >= preeditAreaStart) + r.start += preeditAreaLength; + else if (r.start + r.length >= preeditAreaStart) + r.length += preeditAreaLength; + } + + ranges << r; + formatsChanged = true; + r.start = -1; + } + + if (r.start != -1) { + r.length = formatChanges.count() - r.start; + + if (preeditAreaLength != 0) { + if (r.start >= preeditAreaStart) + r.start += preeditAreaLength; + else if (r.start + r.length >= preeditAreaStart) + r.length += preeditAreaLength; + } + + ranges << r; + formatsChanged = true; + } + + if (formatsChanged) { + layout->setAdditionalFormats(ranges); + doc->markContentsDirty(currentBlock.position(), currentBlock.length()); + } +} + +void QSyntaxHighlighterPrivate::_q_reformatBlocks(int from, int charsRemoved, int charsAdded) +{ + if (!inReformatBlocks) + reformatBlocks(from, charsRemoved, charsAdded); +} + +void QSyntaxHighlighterPrivate::reformatBlocks(int from, int charsRemoved, int charsAdded) +{ + rehighlightPending = false; + + QTextBlock block = doc->findBlock(from); + if (!block.isValid()) + return; + + int endPosition; + QTextBlock lastBlock = doc->findBlock(from + charsAdded + (charsRemoved > 0 ? 1 : 0)); + if (lastBlock.isValid()) + endPosition = lastBlock.position() + lastBlock.length(); + else + endPosition = doc->docHandle()->length(); + + bool forceHighlightOfNextBlock = false; + + while (block.isValid() && (block.position() < endPosition || forceHighlightOfNextBlock)) { + const int stateBeforeHighlight = block.userState(); + + reformatBlock(block); + + forceHighlightOfNextBlock = (block.userState() != stateBeforeHighlight); + + block = block.next(); + } + + formatChanges.clear(); +} + +void QSyntaxHighlighterPrivate::reformatBlock(const QTextBlock &block) +{ + Q_Q(QSyntaxHighlighter); + + Q_ASSERT_X(!currentBlock.isValid(), "QSyntaxHighlighter::reformatBlock()", "reFormatBlock() called recursively"); + + currentBlock = block; + + formatChanges.fill(QTextCharFormat(), block.length() - 1); + q->highlightBlock(block.text()); + applyFormatChanges(); + + currentBlock = QTextBlock(); +} + +/*! + \class QSyntaxHighlighter + \reentrant + + \brief The QSyntaxHighlighter class allows you to define syntax + highlighting rules, and in addition you can use the class to query + a document's current formatting or user data. + + \since 4.1 + + \ingroup richtext-processing + + The QSyntaxHighlighter class is a base class for implementing + QTextEdit syntax highlighters. A syntax highligher automatically + highlights parts of the text in a QTextEdit, or more generally in + a QTextDocument. Syntax highlighters are often used when the user + is entering text in a specific format (for example source code) + and help the user to read the text and identify syntax errors. + + To provide your own syntax highlighting, you must subclass + QSyntaxHighlighter and reimplement highlightBlock(). + + When you create an instance of your QSyntaxHighlighter subclass, + pass it the QTextEdit or QTextDocument that you want the syntax + highlighting to be applied to. For example: + + \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 0 + + After this your highlightBlock() function will be called + automatically whenever necessary. Use your highlightBlock() + function to apply formatting (e.g. setting the font and color) to + the text that is passed to it. QSyntaxHighlighter provides the + setFormat() function which applies a given QTextCharFormat on + the current text block. For example: + + \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 1 + + Some syntaxes can have constructs that span several text + blocks. For example, a C++ syntax highlighter should be able to + cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with + these cases it is necessary to know the end state of the previous + text block (e.g. "in comment"). + + Inside your highlightBlock() implementation you can query the end + state of the previous text block using the previousBlockState() + function. After parsing the block you can save the last state + using setCurrentBlockState(). + + The currentBlockState() and previousBlockState() functions return + an int value. If no state is set, the returned value is -1. You + can designate any other value to identify any given state using + the setCurrentBlockState() function. Once the state is set the + QTextBlock keeps that value until it is set set again or until the + corresponding paragraph of text is deleted. + + For example, if you're writing a simple C++ syntax highlighter, + you might designate 1 to signify "in comment": + + \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 2 + + In the example above, we first set the current block state to + 0. Then, if the previous block ended within a comment, we higlight + from the beginning of the current block (\c {startIndex = + 0}). Otherwise, we search for the given start expression. If the + specified end expression cannot be found in the text block, we + change the current block state by calling setCurrentBlockState(), + and make sure that the rest of the block is higlighted. + + In addition you can query the current formatting and user data + using the format() and currentBlockUserData() functions + respectively. You can also attach user data to the current text + block using the setCurrentBlockUserData() function. + QTextBlockUserData can be used to store custom settings. In the + case of syntax highlighting, it is in particular interesting as + cache storage for information that you may figure out while + parsing the paragraph's text. For an example, see the + setCurrentBlockUserData() documentation. + + \sa QTextEdit, {Syntax Highlighter Example} +*/ + +/*! + Constructs a QSyntaxHighlighter with the given \a parent. +*/ +QSyntaxHighlighter::QSyntaxHighlighter(QObject *parent) + : QObject(*new QSyntaxHighlighterPrivate, parent) +{ +} + +/*! + Constructs a QSyntaxHighlighter and installs it on \a parent. + The specified QTextDocument also becomes the owner of the + QSyntaxHighlighter. +*/ +QSyntaxHighlighter::QSyntaxHighlighter(QTextDocument *parent) + : QObject(*new QSyntaxHighlighterPrivate, parent) +{ + setDocument(parent); +} + +/*! + Constructs a QSyntaxHighlighter and installs it on \a parent 's + QTextDocument. The specified QTextEdit also becomes the owner of + the QSyntaxHighlighter. +*/ +QSyntaxHighlighter::QSyntaxHighlighter(QTextEdit *parent) + : QObject(*new QSyntaxHighlighterPrivate, parent) +{ + setDocument(parent->document()); +} + +/*! + Destructor. Uninstalls this syntax highlighter from the text document. +*/ +QSyntaxHighlighter::~QSyntaxHighlighter() +{ + setDocument(0); +} + +/*! + Installs the syntax highlighter on the given QTextDocument \a doc. + A QSyntaxHighlighter can only be used with one document at a time. +*/ +void QSyntaxHighlighter::setDocument(QTextDocument *doc) +{ + Q_D(QSyntaxHighlighter); + if (d->doc) { + disconnect(d->doc, SIGNAL(contentsChange(int,int,int)), + this, SLOT(_q_reformatBlocks(int,int,int))); + + QTextCursor cursor(d->doc); + cursor.beginEditBlock(); + for (QTextBlock blk = d->doc->begin(); blk.isValid(); blk = blk.next()) + blk.layout()->clearAdditionalFormats(); + cursor.endEditBlock(); + } + d->doc = doc; + if (d->doc) { + connect(d->doc, SIGNAL(contentsChange(int,int,int)), + this, SLOT(_q_reformatBlocks(int,int,int))); + d->rehighlightPending = true; + QTimer::singleShot(0, this, SLOT(_q_delayedRehighlight())); + } +} + +/*! + Returns the QTextDocument on which this syntax highlighter is + installed. +*/ +QTextDocument *QSyntaxHighlighter::document() const +{ + Q_D(const QSyntaxHighlighter); + return d->doc; +} + +/*! + \since 4.2 + + Reapplies the highlighting to the whole document. + + \sa rehighlightBlock() +*/ +void QSyntaxHighlighter::rehighlight() +{ + Q_D(QSyntaxHighlighter); + if (!d->doc) + return; + + QTextCursor cursor(d->doc); + d->rehighlight(cursor, QTextCursor::End); +} + +/*! + \since 4.6 + + Reapplies the highlighting to the given QTextBlock \a block. + + \sa rehighlight() +*/ +void QSyntaxHighlighter::rehighlightBlock(const QTextBlock &block) +{ + Q_D(QSyntaxHighlighter); + if (!d->doc || !block.isValid() || block.document() != d->doc) + return; + + const bool rehighlightPending = d->rehighlightPending; + + QTextCursor cursor(block); + d->rehighlight(cursor, QTextCursor::EndOfBlock); + + if (rehighlightPending) + d->rehighlightPending = rehighlightPending; +} + +/*! + \fn void QSyntaxHighlighter::highlightBlock(const QString &text) + + Highlights the given text block. This function is called when + necessary by the rich text engine, i.e. on text blocks which have + changed. + + To provide your own syntax highlighting, you must subclass + QSyntaxHighlighter and reimplement highlightBlock(). In your + reimplementation you should parse the block's \a text and call + setFormat() as often as necessary to apply any font and color + changes that you require. For example: + + \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 3 + + Some syntaxes can have constructs that span several text + blocks. For example, a C++ syntax highlighter should be able to + cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with + these cases it is necessary to know the end state of the previous + text block (e.g. "in comment"). + + Inside your highlightBlock() implementation you can query the end + state of the previous text block using the previousBlockState() + function. After parsing the block you can save the last state + using setCurrentBlockState(). + + The currentBlockState() and previousBlockState() functions return + an int value. If no state is set, the returned value is -1. You + can designate any other value to identify any given state using + the setCurrentBlockState() function. Once the state is set the + QTextBlock keeps that value until it is set set again or until the + corresponding paragraph of text gets deleted. + + For example, if you're writing a simple C++ syntax highlighter, + you might designate 1 to signify "in comment". For a text block + that ended in the middle of a comment you'd set 1 using + setCurrentBlockState, and for other paragraphs you'd set 0. + In your parsing code if the return value of previousBlockState() + is 1, you would highlight the text as a C++ comment until you + reached the closing \c{*}\c{/}. + + \sa previousBlockState(), setFormat(), setCurrentBlockState() +*/ + +/*! + This function is applied to the syntax highlighter's current text + block (i.e. the text that is passed to the highlightBlock() + function). + + The specified \a format is applied to the text from the \a start + position for a length of \a count characters (if \a count is 0, + nothing is done). The formatting properties set in \a format are + merged at display time with the formatting information stored + directly in the document, for example as previously set with + QTextCursor's functions. Note that the document itself remains + unmodified by the format set through this function. + + \sa format(), highlightBlock() +*/ +void QSyntaxHighlighter::setFormat(int start, int count, const QTextCharFormat &format) +{ + Q_D(QSyntaxHighlighter); + if (start < 0 || start >= d->formatChanges.count()) + return; + + const int end = qMin(start + count, d->formatChanges.count()); + for (int i = start; i < end; ++i) + d->formatChanges[i] = format; +} + +/*! + \overload + + The specified \a color is applied to the current text block from + the \a start position for a length of \a count characters. + + The other attributes of the current text block, e.g. the font and + background color, are reset to default values. + + \sa format(), highlightBlock() +*/ +void QSyntaxHighlighter::setFormat(int start, int count, const QColor &color) +{ + QTextCharFormat format; + format.setForeground(color); + setFormat(start, count, format); +} + +/*! + \overload + + The specified \a font is applied to the current text block from + the \a start position for a length of \a count characters. + + The other attributes of the current text block, e.g. the font and + background color, are reset to default values. + + \sa format(), highlightBlock() +*/ +void QSyntaxHighlighter::setFormat(int start, int count, const QFont &font) +{ + QTextCharFormat format; + format.setFont(font); + setFormat(start, count, format); +} + +/*! + \fn QTextCharFormat QSyntaxHighlighter::format(int position) const + + Returns the format at \a position inside the syntax highlighter's + current text block. +*/ +QTextCharFormat QSyntaxHighlighter::format(int pos) const +{ + Q_D(const QSyntaxHighlighter); + if (pos < 0 || pos >= d->formatChanges.count()) + return QTextCharFormat(); + return d->formatChanges.at(pos); +} + +/*! + Returns the end state of the text block previous to the + syntax highlighter's current block. If no value was + previously set, the returned value is -1. + + \sa highlightBlock(), setCurrentBlockState() +*/ +int QSyntaxHighlighter::previousBlockState() const +{ + Q_D(const QSyntaxHighlighter); + if (!d->currentBlock.isValid()) + return -1; + + const QTextBlock previous = d->currentBlock.previous(); + if (!previous.isValid()) + return -1; + + return previous.userState(); +} + +/*! + Returns the state of the current text block. If no value is set, + the returned value is -1. +*/ +int QSyntaxHighlighter::currentBlockState() const +{ + Q_D(const QSyntaxHighlighter); + if (!d->currentBlock.isValid()) + return -1; + + return d->currentBlock.userState(); +} + +/*! + Sets the state of the current text block to \a newState. + + \sa highlightBlock() +*/ +void QSyntaxHighlighter::setCurrentBlockState(int newState) +{ + Q_D(QSyntaxHighlighter); + if (!d->currentBlock.isValid()) + return; + + d->currentBlock.setUserState(newState); +} + +/*! + Attaches the given \a data to the current text block. The + ownership is passed to the underlying text document, i.e. the + provided QTextBlockUserData object will be deleted if the + corresponding text block gets deleted. + + QTextBlockUserData can be used to store custom settings. In the + case of syntax highlighting, it is in particular interesting as + cache storage for information that you may figure out while + parsing the paragraph's text. + + For example while parsing the text, you can keep track of + parenthesis characters that you encounter ('{[(' and the like), + and store their relative position and the actual QChar in a simple + class derived from QTextBlockUserData: + + \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 4 + + During cursor navigation in the associated editor, you can ask the + current QTextBlock (retrieved using the QTextCursor::block() + function) if it has a user data object set and cast it to your \c + BlockData object. Then you can check if the current cursor + position matches with a previously recorded parenthesis position, + and, depending on the type of parenthesis (opening or closing), + find the next opening or closing parenthesis on the same level. + + In this way you can do a visual parenthesis matching and highlight + from the current cursor position to the matching parenthesis. That + makes it easier to spot a missing parenthesis in your code and to + find where a corresponding opening/closing parenthesis is when + editing parenthesis intensive code. + + \sa QTextBlock::setUserData() +*/ +void QSyntaxHighlighter::setCurrentBlockUserData(QTextBlockUserData *data) +{ + Q_D(QSyntaxHighlighter); + if (!d->currentBlock.isValid()) + return; + + d->currentBlock.setUserData(data); +} + +/*! + Returns the QTextBlockUserData object previously attached to the + current text block. + + \sa QTextBlock::userData(), setCurrentBlockUserData() +*/ +QTextBlockUserData *QSyntaxHighlighter::currentBlockUserData() const +{ + Q_D(const QSyntaxHighlighter); + if (!d->currentBlock.isValid()) + return 0; + + return d->currentBlock.userData(); +} + +/*! + \since 4.4 + + Returns the current text block. +*/ +QTextBlock QSyntaxHighlighter::currentBlock() const +{ + Q_D(const QSyntaxHighlighter); + return d->currentBlock; +} + +QT_END_NAMESPACE + +#include "moc_qsyntaxhighlighter.cpp" + +#endif // QT_NO_SYNTAXHIGHLIGHTER diff --git a/src/gui/text/qsyntaxhighlighter.h b/src/gui/text/qsyntaxhighlighter.h new file mode 100644 index 0000000000..9cd5778074 --- /dev/null +++ b/src/gui/text/qsyntaxhighlighter.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSYNTAXHIGHLIGHTER_H +#define QSYNTAXHIGHLIGHTER_H + +#include + +#ifndef QT_NO_SYNTAXHIGHLIGHTER + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QTextDocument; +class QSyntaxHighlighterPrivate; +class QTextCharFormat; +class QFont; +class QColor; +class QTextBlockUserData; +class QTextEdit; + +class Q_GUI_EXPORT QSyntaxHighlighter : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSyntaxHighlighter) +public: + QSyntaxHighlighter(QObject *parent); + QSyntaxHighlighter(QTextDocument *parent); + QSyntaxHighlighter(QTextEdit *parent); + virtual ~QSyntaxHighlighter(); + + void setDocument(QTextDocument *doc); + QTextDocument *document() const; + +public Q_SLOTS: + void rehighlight(); + void rehighlightBlock(const QTextBlock &block); + +protected: + virtual void highlightBlock(const QString &text) = 0; + + void setFormat(int start, int count, const QTextCharFormat &format); + void setFormat(int start, int count, const QColor &color); + void setFormat(int start, int count, const QFont &font); + QTextCharFormat format(int pos) const; + + int previousBlockState() const; + int currentBlockState() const; + void setCurrentBlockState(int newState); + + void setCurrentBlockUserData(QTextBlockUserData *data); + QTextBlockUserData *currentBlockUserData() const; + + QTextBlock currentBlock() const; + +private: + Q_DISABLE_COPY(QSyntaxHighlighter) + Q_PRIVATE_SLOT(d_func(), void _q_reformatBlocks(int from, int charsRemoved, int charsAdded)) + Q_PRIVATE_SLOT(d_func(), void _q_delayedRehighlight()) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_NO_SYNTAXHIGHLIGHTER + +#endif // QSYNTAXHIGHLIGHTER_H diff --git a/src/gui/text/qtextcontrol.cpp b/src/gui/text/qtextcontrol.cpp new file mode 100644 index 0000000000..43967307bc --- /dev/null +++ b/src/gui/text/qtextcontrol.cpp @@ -0,0 +1,3148 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextcontrol_p.h" +#include "qtextcontrol_p_p.h" + +#ifndef QT_NO_TEXTCONTROL + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "private/qtextdocumentlayout_p.h" +#include "private/qabstracttextdocumentlayout_p.h" +#include "private/qtextedit_p.h" +#include "qtextdocument.h" +#include "private/qtextdocument_p.h" +#include "qtextlist.h" +#include "private/qtextcontrol_p.h" +#include "qgraphicssceneevent.h" +#include "qprinter.h" +#include "qtextdocumentwriter.h" +#include "private/qtextcursor_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef QT_NO_SHORTCUT +#include "private/qapplication_p.h" +#include "private/qshortcutmap_p.h" +#include +#define ACCEL_KEY(k) (!qApp->d_func()->shortcutMap.hasShortcutForKeySequence(k) ? QLatin1Char('\t') + QString(QKeySequence(k)) : QString()) +#else +#define ACCEL_KEY(k) QString() +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_CONTEXTMENU +#if defined(Q_WS_WIN) || defined(Q_WS_X11) +extern bool qt_use_rtl_extensions; +#endif +#endif + +// could go into QTextCursor... +static QTextLine currentTextLine(const QTextCursor &cursor) +{ + const QTextBlock block = cursor.block(); + if (!block.isValid()) + return QTextLine(); + + const QTextLayout *layout = block.layout(); + if (!layout) + return QTextLine(); + + const int relativePos = cursor.position() - block.position(); + return layout->lineForTextPosition(relativePos); +} + +QTextControlPrivate::QTextControlPrivate() + : doc(0), cursorOn(false), cursorIsFocusIndicator(false), + interactionFlags(Qt::TextEditorInteraction), + dragEnabled(true), +#ifndef QT_NO_DRAGANDDROP + mousePressed(false), mightStartDrag(false), +#endif + lastSelectionState(false), ignoreAutomaticScrollbarAdjustement(false), + overwriteMode(false), + acceptRichText(true), + preeditCursor(0), hideCursor(false), + hasFocus(false), +#ifdef QT_KEYPAD_NAVIGATION + hasEditFocus(false), +#endif + isEnabled(true), + hadSelectionOnMousePress(false), + ignoreUnusedNavigationEvents(false), + openExternalLinks(false), + wordSelectionEnabled(false) +{} + +bool QTextControlPrivate::cursorMoveKeyEvent(QKeyEvent *e) +{ +#ifdef QT_NO_SHORTCUT + Q_UNUSED(e); +#endif + + Q_Q(QTextControl); + if (cursor.isNull()) + return false; + + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + + QTextCursor::MoveMode mode = QTextCursor::MoveAnchor; + QTextCursor::MoveOperation op = QTextCursor::NoMove; + + if (false) { + } +#ifndef QT_NO_SHORTCUT + if (e == QKeySequence::MoveToNextChar) { + op = QTextCursor::Right; + } + else if (e == QKeySequence::MoveToPreviousChar) { + op = QTextCursor::Left; + } + else if (e == QKeySequence::SelectNextChar) { + op = QTextCursor::Right; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectPreviousChar) { + op = QTextCursor::Left; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectNextWord) { + op = QTextCursor::WordRight; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectPreviousWord) { + op = QTextCursor::WordLeft; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectStartOfLine) { + op = QTextCursor::StartOfLine; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectEndOfLine) { + op = QTextCursor::EndOfLine; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectStartOfBlock) { + op = QTextCursor::StartOfBlock; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectEndOfBlock) { + op = QTextCursor::EndOfBlock; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectStartOfDocument) { + op = QTextCursor::Start; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectEndOfDocument) { + op = QTextCursor::End; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectPreviousLine) { + op = QTextCursor::Up; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectNextLine) { + op = QTextCursor::Down; + mode = QTextCursor::KeepAnchor; + { + QTextBlock block = cursor.block(); + QTextLine line = currentTextLine(cursor); + if (!block.next().isValid() + && line.isValid() + && line.lineNumber() == block.layout()->lineCount() - 1) + op = QTextCursor::End; + } + } + else if (e == QKeySequence::MoveToNextWord) { + op = QTextCursor::WordRight; + } + else if (e == QKeySequence::MoveToPreviousWord) { + op = QTextCursor::WordLeft; + } + else if (e == QKeySequence::MoveToEndOfBlock) { + op = QTextCursor::EndOfBlock; + } + else if (e == QKeySequence::MoveToStartOfBlock) { + op = QTextCursor::StartOfBlock; + } + else if (e == QKeySequence::MoveToNextLine) { + op = QTextCursor::Down; + } + else if (e == QKeySequence::MoveToPreviousLine) { + op = QTextCursor::Up; + } + else if (e == QKeySequence::MoveToPreviousLine) { + op = QTextCursor::Up; + } + else if (e == QKeySequence::MoveToStartOfLine) { + op = QTextCursor::StartOfLine; + } + else if (e == QKeySequence::MoveToEndOfLine) { + op = QTextCursor::EndOfLine; + } + else if (e == QKeySequence::MoveToStartOfDocument) { + op = QTextCursor::Start; + } + else if (e == QKeySequence::MoveToEndOfDocument) { + op = QTextCursor::End; + } +#endif // QT_NO_SHORTCUT + else { + return false; + } + +// Except for pageup and pagedown, Mac OS X has very different behavior, we don't do it all, but +// here's the breakdown: +// Shift still works as an anchor, but only one of the other keys can be down Ctrl (Command), +// Alt (Option), or Meta (Control). +// Command/Control + Left/Right -- Move to left or right of the line +// + Up/Down -- Move to top bottom of the file. (Control doesn't move the cursor) +// Option + Left/Right -- Move one word Left/right. +// + Up/Down -- Begin/End of Paragraph. +// Home/End Top/Bottom of file. (usually don't move the cursor, but will select) + + bool visualNavigation = cursor.visualNavigation(); + cursor.setVisualNavigation(true); + const bool moved = cursor.movePosition(op, mode); + cursor.setVisualNavigation(visualNavigation); + q->ensureCursorVisible(); + + bool ignoreNavigationEvents = ignoreUnusedNavigationEvents; + bool isNavigationEvent = e->key() == Qt::Key_Up || e->key() == Qt::Key_Down; + +#ifdef QT_KEYPAD_NAVIGATION + ignoreNavigationEvents = ignoreNavigationEvents || QApplication::keypadNavigationEnabled(); + isNavigationEvent = isNavigationEvent || + (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional + && (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right)); +#else + isNavigationEvent = isNavigationEvent || e->key() == Qt::Key_Left || e->key() == Qt::Key_Right; +#endif + + if (moved) { + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + emit q->microFocusChanged(); + } else if (ignoreNavigationEvents && isNavigationEvent && oldSelection.anchor() == cursor.anchor()) { + return false; + } + + selectionChanged(/*forceEmitSelectionChanged =*/(mode == QTextCursor::KeepAnchor)); + + repaintOldAndNewSelection(oldSelection); + + return true; +} + +void QTextControlPrivate::updateCurrentCharFormat() +{ + Q_Q(QTextControl); + + QTextCharFormat fmt = cursor.charFormat(); + if (fmt == lastCharFormat) + return; + lastCharFormat = fmt; + + emit q->currentCharFormatChanged(fmt); + emit q->microFocusChanged(); +} + +void QTextControlPrivate::indent() +{ + QTextBlockFormat blockFmt = cursor.blockFormat(); + + QTextList *list = cursor.currentList(); + if (!list) { + QTextBlockFormat modifier; + modifier.setIndent(blockFmt.indent() + 1); + cursor.mergeBlockFormat(modifier); + } else { + QTextListFormat format = list->format(); + format.setIndent(format.indent() + 1); + + if (list->itemNumber(cursor.block()) == 1) + list->setFormat(format); + else + cursor.createList(format); + } +} + +void QTextControlPrivate::outdent() +{ + QTextBlockFormat blockFmt = cursor.blockFormat(); + + QTextList *list = cursor.currentList(); + + if (!list) { + QTextBlockFormat modifier; + modifier.setIndent(blockFmt.indent() - 1); + cursor.mergeBlockFormat(modifier); + } else { + QTextListFormat listFmt = list->format(); + listFmt.setIndent(listFmt.indent() - 1); + list->setFormat(listFmt); + } +} + +void QTextControlPrivate::gotoNextTableCell() +{ + QTextTable *table = cursor.currentTable(); + QTextTableCell cell = table->cellAt(cursor); + + int newColumn = cell.column() + cell.columnSpan(); + int newRow = cell.row(); + + if (newColumn >= table->columns()) { + newColumn = 0; + ++newRow; + if (newRow >= table->rows()) + table->insertRows(table->rows(), 1); + } + + cell = table->cellAt(newRow, newColumn); + cursor = cell.firstCursorPosition(); +} + +void QTextControlPrivate::gotoPreviousTableCell() +{ + QTextTable *table = cursor.currentTable(); + QTextTableCell cell = table->cellAt(cursor); + + int newColumn = cell.column() - 1; + int newRow = cell.row(); + + if (newColumn < 0) { + newColumn = table->columns() - 1; + --newRow; + if (newRow < 0) + return; + } + + cell = table->cellAt(newRow, newColumn); + cursor = cell.firstCursorPosition(); +} + +void QTextControlPrivate::createAutoBulletList() +{ + cursor.beginEditBlock(); + + QTextBlockFormat blockFmt = cursor.blockFormat(); + + QTextListFormat listFmt; + listFmt.setStyle(QTextListFormat::ListDisc); + listFmt.setIndent(blockFmt.indent() + 1); + + blockFmt.setIndent(0); + cursor.setBlockFormat(blockFmt); + + cursor.createList(listFmt); + + cursor.endEditBlock(); +} + +void QTextControlPrivate::init(Qt::TextFormat format, const QString &text, QTextDocument *document) +{ + Q_Q(QTextControl); + setContent(format, text, document); + + doc->setUndoRedoEnabled(interactionFlags & Qt::TextEditable); + q->setCursorWidth(-1); +} + +void QTextControlPrivate::setContent(Qt::TextFormat format, const QString &text, QTextDocument *document) +{ + Q_Q(QTextControl); + + // for use when called from setPlainText. we may want to re-use the currently + // set char format then. + const QTextCharFormat charFormatForInsertion = cursor.charFormat(); + + bool clearDocument = true; + if (!doc) { + if (document) { + doc = document; + clearDocument = false; + } else { + palette = QApplication::palette("QTextControl"); + doc = new QTextDocument(q); + } + _q_documentLayoutChanged(); + cursor = QTextCursor(doc); + +// #### doc->documentLayout()->setPaintDevice(viewport); + + QObject::connect(doc, SIGNAL(contentsChanged()), q, SLOT(_q_updateCurrentCharFormatAndSelection())); + QObject::connect(doc, SIGNAL(cursorPositionChanged(QTextCursor)), q, SLOT(_q_emitCursorPosChanged(QTextCursor))); + QObject::connect(doc, SIGNAL(documentLayoutChanged()), q, SLOT(_q_documentLayoutChanged())); + + // convenience signal forwards + QObject::connect(doc, SIGNAL(undoAvailable(bool)), q, SIGNAL(undoAvailable(bool))); + QObject::connect(doc, SIGNAL(redoAvailable(bool)), q, SIGNAL(redoAvailable(bool))); + QObject::connect(doc, SIGNAL(modificationChanged(bool)), q, SIGNAL(modificationChanged(bool))); + QObject::connect(doc, SIGNAL(blockCountChanged(int)), q, SIGNAL(blockCountChanged(int))); + } + + bool previousUndoRedoState = doc->isUndoRedoEnabled(); + if (!document) + doc->setUndoRedoEnabled(false); + + //Saving the index save some time. + static int contentsChangedIndex = QTextDocument::staticMetaObject.indexOfSignal("contentsChanged()"); + static int textChangedIndex = QTextControl::staticMetaObject.indexOfSignal("textChanged()"); + // avoid multiple textChanged() signals being emitted + QMetaObject::disconnect(doc, contentsChangedIndex, q, textChangedIndex); + + if (!text.isEmpty()) { + // clear 'our' cursor for insertion to prevent + // the emission of the cursorPositionChanged() signal. + // instead we emit it only once at the end instead of + // at the end of the document after loading and when + // positioning the cursor again to the start of the + // document. + cursor = QTextCursor(); + if (format == Qt::PlainText) { + QTextCursor formatCursor(doc); + // put the setPlainText and the setCharFormat into one edit block, + // so that the syntax highlight triggers only /once/ for the entire + // document, not twice. + formatCursor.beginEditBlock(); + doc->setPlainText(text); + doc->setUndoRedoEnabled(false); + formatCursor.select(QTextCursor::Document); + formatCursor.setCharFormat(charFormatForInsertion); + formatCursor.endEditBlock(); + } else { +#ifndef QT_NO_TEXTHTMLPARSER + doc->setHtml(text); +#else + doc->setPlainText(text); +#endif + doc->setUndoRedoEnabled(false); + } + cursor = QTextCursor(doc); + } else if (clearDocument) { + doc->clear(); + } + cursor.setCharFormat(charFormatForInsertion); + + QMetaObject::connect(doc, contentsChangedIndex, q, textChangedIndex); + emit q->textChanged(); + if (!document) + doc->setUndoRedoEnabled(previousUndoRedoState); + _q_updateCurrentCharFormatAndSelection(); + if (!document) + doc->setModified(false); + + q->ensureCursorVisible(); + emit q->cursorPositionChanged(); +} + +void QTextControlPrivate::startDrag() +{ +#ifndef QT_NO_DRAGANDDROP + Q_Q(QTextControl); + mousePressed = false; + if (!contextWidget) + return; + QMimeData *data = q->createMimeDataFromSelection(); + + QDrag *drag = new QDrag(contextWidget); + drag->setMimeData(data); + + Qt::DropActions actions = Qt::CopyAction; + Qt::DropAction action; + if (interactionFlags & Qt::TextEditable) { + actions |= Qt::MoveAction; + action = drag->exec(actions, Qt::MoveAction); + } else { + action = drag->exec(actions, Qt::CopyAction); + } + + if (action == Qt::MoveAction && drag->target() != contextWidget) + cursor.removeSelectedText(); +#endif +} + +void QTextControlPrivate::setCursorPosition(const QPointF &pos) +{ + Q_Q(QTextControl); + const int cursorPos = q->hitTest(pos, Qt::FuzzyHit); + if (cursorPos == -1) + return; + cursor.setPosition(cursorPos); +} + +void QTextControlPrivate::setCursorPosition(int pos, QTextCursor::MoveMode mode) +{ + cursor.setPosition(pos, mode); + + if (mode != QTextCursor::KeepAnchor) { + selectedWordOnDoubleClick = QTextCursor(); + selectedBlockOnTrippleClick = QTextCursor(); + } +} + +void QTextControlPrivate::repaintCursor() +{ + Q_Q(QTextControl); + emit q->updateRequest(cursorRectPlusUnicodeDirectionMarkers(cursor)); +} + +void QTextControlPrivate::repaintOldAndNewSelection(const QTextCursor &oldSelection) +{ + Q_Q(QTextControl); + if (cursor.hasSelection() + && oldSelection.hasSelection() + && cursor.currentFrame() == oldSelection.currentFrame() + && !cursor.hasComplexSelection() + && !oldSelection.hasComplexSelection() + && cursor.anchor() == oldSelection.anchor() + ) { + QTextCursor differenceSelection(doc); + differenceSelection.setPosition(oldSelection.position()); + differenceSelection.setPosition(cursor.position(), QTextCursor::KeepAnchor); + emit q->updateRequest(q->selectionRect(differenceSelection)); + } else { + if (!oldSelection.isNull()) + emit q->updateRequest(q->selectionRect(oldSelection) | cursorRectPlusUnicodeDirectionMarkers(oldSelection)); + emit q->updateRequest(q->selectionRect() | cursorRectPlusUnicodeDirectionMarkers(cursor)); + } +} + +void QTextControlPrivate::selectionChanged(bool forceEmitSelectionChanged /*=false*/) +{ + Q_Q(QTextControl); + if (forceEmitSelectionChanged) + emit q->selectionChanged(); + + bool current = cursor.hasSelection(); + if (current == lastSelectionState) + return; + + lastSelectionState = current; + emit q->copyAvailable(current); + if (!forceEmitSelectionChanged) + emit q->selectionChanged(); + emit q->microFocusChanged(); +} + +void QTextControlPrivate::_q_updateCurrentCharFormatAndSelection() +{ + updateCurrentCharFormat(); + selectionChanged(); +} + +#ifndef QT_NO_CLIPBOARD +void QTextControlPrivate::setClipboardSelection() +{ + QClipboard *clipboard = QApplication::clipboard(); + if (!cursor.hasSelection() || !clipboard->supportsSelection()) + return; + Q_Q(QTextControl); + QMimeData *data = q->createMimeDataFromSelection(); + clipboard->setMimeData(data, QClipboard::Selection); +} +#endif + +void QTextControlPrivate::_q_emitCursorPosChanged(const QTextCursor &someCursor) +{ + Q_Q(QTextControl); + if (someCursor.isCopyOf(cursor)) { + emit q->cursorPositionChanged(); + emit q->microFocusChanged(); + } +} + +void QTextControlPrivate::_q_documentLayoutChanged() +{ + Q_Q(QTextControl); + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + QObject::connect(layout, SIGNAL(update(QRectF)), q, SIGNAL(updateRequest(QRectF))); + QObject::connect(layout, SIGNAL(updateBlock(QTextBlock)), q, SLOT(_q_updateBlock(QTextBlock))); + QObject::connect(layout, SIGNAL(documentSizeChanged(QSizeF)), q, SIGNAL(documentSizeChanged(QSizeF))); + +} + +void QTextControlPrivate::setBlinkingCursorEnabled(bool enable) +{ + Q_Q(QTextControl); + + if (enable && QApplication::cursorFlashTime() > 0) + cursorBlinkTimer.start(QApplication::cursorFlashTime() / 2, q); + else + cursorBlinkTimer.stop(); + + cursorOn = enable; + + repaintCursor(); +} + +void QTextControlPrivate::extendWordwiseSelection(int suggestedNewPosition, qreal mouseXPosition) +{ + Q_Q(QTextControl); + + // if inside the initial selected word keep that + if (suggestedNewPosition >= selectedWordOnDoubleClick.selectionStart() + && suggestedNewPosition <= selectedWordOnDoubleClick.selectionEnd()) { + q->setTextCursor(selectedWordOnDoubleClick); + return; + } + + QTextCursor curs = selectedWordOnDoubleClick; + curs.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor); + + if (!curs.movePosition(QTextCursor::StartOfWord)) + return; + const int wordStartPos = curs.position(); + + const int blockPos = curs.block().position(); + const QPointF blockCoordinates = q->blockBoundingRect(curs.block()).topLeft(); + + QTextLine line = currentTextLine(curs); + if (!line.isValid()) + return; + + const qreal wordStartX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x(); + + if (!curs.movePosition(QTextCursor::EndOfWord)) + return; + const int wordEndPos = curs.position(); + + const QTextLine otherLine = currentTextLine(curs); + if (otherLine.textStart() != line.textStart() + || wordEndPos == wordStartPos) + return; + + const qreal wordEndX = line.cursorToX(curs.position() - blockPos) + blockCoordinates.x(); + + if (mouseXPosition < wordStartX || mouseXPosition > wordEndX) + return; + + // keep the already selected word even when moving to the left + // (#39164) + if (suggestedNewPosition < selectedWordOnDoubleClick.position()) + cursor.setPosition(selectedWordOnDoubleClick.selectionEnd()); + else + cursor.setPosition(selectedWordOnDoubleClick.selectionStart()); + + const qreal differenceToStart = mouseXPosition - wordStartX; + const qreal differenceToEnd = wordEndX - mouseXPosition; + + if (differenceToStart < differenceToEnd) + setCursorPosition(wordStartPos, QTextCursor::KeepAnchor); + else + setCursorPosition(wordEndPos, QTextCursor::KeepAnchor); + + if (interactionFlags & Qt::TextSelectableByMouse) { +#ifndef QT_NO_CLIPBOARD + setClipboardSelection(); +#endif + selectionChanged(true); + } +} + +void QTextControlPrivate::extendBlockwiseSelection(int suggestedNewPosition) +{ + Q_Q(QTextControl); + + // if inside the initial selected line keep that + if (suggestedNewPosition >= selectedBlockOnTrippleClick.selectionStart() + && suggestedNewPosition <= selectedBlockOnTrippleClick.selectionEnd()) { + q->setTextCursor(selectedBlockOnTrippleClick); + return; + } + + if (suggestedNewPosition < selectedBlockOnTrippleClick.position()) { + cursor.setPosition(selectedBlockOnTrippleClick.selectionEnd()); + cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); + } else { + cursor.setPosition(selectedBlockOnTrippleClick.selectionStart()); + cursor.setPosition(suggestedNewPosition, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + } + + if (interactionFlags & Qt::TextSelectableByMouse) { +#ifndef QT_NO_CLIPBOARD + setClipboardSelection(); +#endif + selectionChanged(true); + } +} + +void QTextControlPrivate::_q_deleteSelected() +{ + if (!(interactionFlags & Qt::TextEditable) || !cursor.hasSelection()) + return; + cursor.removeSelectedText(); +} + +void QTextControl::undo() +{ + Q_D(QTextControl); + d->repaintSelection(); + const int oldCursorPos = d->cursor.position(); + d->doc->undo(&d->cursor); + if (d->cursor.position() != oldCursorPos) + emit cursorPositionChanged(); + emit microFocusChanged(); + ensureCursorVisible(); +} + +void QTextControl::redo() +{ + Q_D(QTextControl); + d->repaintSelection(); + const int oldCursorPos = d->cursor.position(); + d->doc->redo(&d->cursor); + if (d->cursor.position() != oldCursorPos) + emit cursorPositionChanged(); + emit microFocusChanged(); + ensureCursorVisible(); +} + +QTextControl::QTextControl(QObject *parent) + : QObject(*new QTextControlPrivate, parent) +{ + Q_D(QTextControl); + d->init(); +} + +QTextControl::QTextControl(const QString &text, QObject *parent) + : QObject(*new QTextControlPrivate, parent) +{ + Q_D(QTextControl); + d->init(Qt::RichText, text); +} + +QTextControl::QTextControl(QTextDocument *doc, QObject *parent) + : QObject(*new QTextControlPrivate, parent) +{ + Q_D(QTextControl); + d->init(Qt::RichText, QString(), doc); +} + +QTextControl::~QTextControl() +{ +} + +void QTextControl::setDocument(QTextDocument *document) +{ + Q_D(QTextControl); + if (d->doc == document) + return; + + d->doc->disconnect(this); + d->doc->documentLayout()->disconnect(this); + d->doc->documentLayout()->setPaintDevice(0); + + if (d->doc->parent() == this) + delete d->doc; + + d->doc = 0; + d->setContent(Qt::RichText, QString(), document); +} + +QTextDocument *QTextControl::document() const +{ + Q_D(const QTextControl); + return d->doc; +} + +void QTextControl::setTextCursor(const QTextCursor &cursor) +{ + Q_D(QTextControl); + d->cursorIsFocusIndicator = false; + const bool posChanged = cursor.position() != d->cursor.position(); + const QTextCursor oldSelection = d->cursor; + d->cursor = cursor; + d->cursorOn = d->hasFocus && (d->interactionFlags & Qt::TextEditable); + d->_q_updateCurrentCharFormatAndSelection(); + ensureCursorVisible(); + d->repaintOldAndNewSelection(oldSelection); + if (posChanged) + emit cursorPositionChanged(); +} + +QTextCursor QTextControl::textCursor() const +{ + Q_D(const QTextControl); + return d->cursor; +} + +#ifndef QT_NO_CLIPBOARD + +void QTextControl::cut() +{ + Q_D(QTextControl); + if (!(d->interactionFlags & Qt::TextEditable) || !d->cursor.hasSelection()) + return; + copy(); + d->cursor.removeSelectedText(); +} + +void QTextControl::copy() +{ + Q_D(QTextControl); + if (!d->cursor.hasSelection()) + return; + QMimeData *data = createMimeDataFromSelection(); + QApplication::clipboard()->setMimeData(data); +} + +void QTextControl::paste(QClipboard::Mode mode) +{ + const QMimeData *md = QApplication::clipboard()->mimeData(mode); + if (md) + insertFromMimeData(md); +} +#endif + +void QTextControl::clear() +{ + Q_D(QTextControl); + // clears and sets empty content + d->extraSelections.clear(); + d->setContent(); +} + + +void QTextControl::selectAll() +{ + Q_D(QTextControl); + const int selectionLength = qAbs(d->cursor.position() - d->cursor.anchor()); + d->cursor.select(QTextCursor::Document); + d->selectionChanged(selectionLength != qAbs(d->cursor.position() - d->cursor.anchor())); + d->cursorIsFocusIndicator = false; + emit updateRequest(); +} + +void QTextControl::processEvent(QEvent *e, const QPointF &coordinateOffset, QWidget *contextWidget) +{ + QMatrix m; + m.translate(coordinateOffset.x(), coordinateOffset.y()); + processEvent(e, m, contextWidget); +} + +void QTextControl::processEvent(QEvent *e, const QMatrix &matrix, QWidget *contextWidget) +{ + Q_D(QTextControl); + if (d->interactionFlags == Qt::NoTextInteraction) { + e->ignore(); + return; + } + + d->contextWidget = contextWidget; + + if (!d->contextWidget) { + switch (e->type()) { +#ifndef QT_NO_GRAPHICSVIEW + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseDoubleClick: + case QEvent::GraphicsSceneContextMenu: + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverMove: + case QEvent::GraphicsSceneHoverLeave: + case QEvent::GraphicsSceneHelp: + case QEvent::GraphicsSceneDragEnter: + case QEvent::GraphicsSceneDragMove: + case QEvent::GraphicsSceneDragLeave: + case QEvent::GraphicsSceneDrop: { + QGraphicsSceneEvent *ev = static_cast(e); + d->contextWidget = ev->widget(); + break; + } +#endif // QT_NO_GRAPHICSVIEW + default: break; + }; + } + + switch (e->type()) { + case QEvent::KeyPress: + d->keyPressEvent(static_cast(e)); + break; + case QEvent::MouseButtonPress: { + QMouseEvent *ev = static_cast(e); + d->mousePressEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), + ev->buttons(), ev->globalPos()); + break; } + case QEvent::MouseMove: { + QMouseEvent *ev = static_cast(e); + d->mouseMoveEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), + ev->buttons(), ev->globalPos()); + break; } + case QEvent::MouseButtonRelease: { + QMouseEvent *ev = static_cast(e); + d->mouseReleaseEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), + ev->buttons(), ev->globalPos()); + break; } + case QEvent::MouseButtonDblClick: { + QMouseEvent *ev = static_cast(e); + d->mouseDoubleClickEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), + ev->buttons(), ev->globalPos()); + break; } + case QEvent::InputMethod: + d->inputMethodEvent(static_cast(e)); + break; +#ifndef QT_NO_CONTEXTMENU + case QEvent::ContextMenu: { + QContextMenuEvent *ev = static_cast(e); + d->contextMenuEvent(ev->globalPos(), matrix.map(ev->pos()), contextWidget); + break; } +#endif // QT_NO_CONTEXTMENU + case QEvent::FocusIn: + case QEvent::FocusOut: + d->focusEvent(static_cast(e)); + break; + + case QEvent::EnabledChange: + d->isEnabled = e->isAccepted(); + break; + +#ifndef QT_NO_TOOLTIP + case QEvent::ToolTip: { + QHelpEvent *ev = static_cast(e); + d->showToolTip(ev->globalPos(), matrix.map(ev->pos()), contextWidget); + break; + } +#endif // QT_NO_TOOLTIP + +#ifndef QT_NO_DRAGANDDROP + case QEvent::DragEnter: { + QDragEnterEvent *ev = static_cast(e); + if (d->dragEnterEvent(e, ev->mimeData())) + ev->acceptProposedAction(); + break; + } + case QEvent::DragLeave: + d->dragLeaveEvent(); + break; + case QEvent::DragMove: { + QDragMoveEvent *ev = static_cast(e); + if (d->dragMoveEvent(e, ev->mimeData(), matrix.map(ev->pos()))) + ev->acceptProposedAction(); + break; + } + case QEvent::Drop: { + QDropEvent *ev = static_cast(e); + if (d->dropEvent(ev->mimeData(), matrix.map(ev->pos()), ev->dropAction(), ev->source())) + ev->acceptProposedAction(); + break; + } +#endif + +#ifndef QT_NO_GRAPHICSVIEW + case QEvent::GraphicsSceneMousePress: { + QGraphicsSceneMouseEvent *ev = static_cast(e); + d->mousePressEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), + ev->screenPos()); + break; } + case QEvent::GraphicsSceneMouseMove: { + QGraphicsSceneMouseEvent *ev = static_cast(e); + d->mouseMoveEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), + ev->screenPos()); + break; } + case QEvent::GraphicsSceneMouseRelease: { + QGraphicsSceneMouseEvent *ev = static_cast(e); + d->mouseReleaseEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), + ev->screenPos()); + break; } + case QEvent::GraphicsSceneMouseDoubleClick: { + QGraphicsSceneMouseEvent *ev = static_cast(e); + d->mouseDoubleClickEvent(ev, ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(), + ev->screenPos()); + break; } + case QEvent::GraphicsSceneContextMenu: { + QGraphicsSceneContextMenuEvent *ev = static_cast(e); + d->contextMenuEvent(ev->screenPos(), matrix.map(ev->pos()), contextWidget); + break; } + + case QEvent::GraphicsSceneHoverMove: { + QGraphicsSceneHoverEvent *ev = static_cast(e); + d->mouseMoveEvent(ev, Qt::NoButton, matrix.map(ev->pos()), ev->modifiers(),Qt::NoButton, + ev->screenPos()); + break; } + + case QEvent::GraphicsSceneDragEnter: { + QGraphicsSceneDragDropEvent *ev = static_cast(e); + if (d->dragEnterEvent(e, ev->mimeData())) + ev->acceptProposedAction(); + break; } + case QEvent::GraphicsSceneDragLeave: + d->dragLeaveEvent(); + break; + case QEvent::GraphicsSceneDragMove: { + QGraphicsSceneDragDropEvent *ev = static_cast(e); + if (d->dragMoveEvent(e, ev->mimeData(), matrix.map(ev->pos()))) + ev->acceptProposedAction(); + break; } + case QEvent::GraphicsSceneDrop: { + QGraphicsSceneDragDropEvent *ev = static_cast(e); + if (d->dropEvent(ev->mimeData(), matrix.map(ev->pos()), ev->dropAction(), ev->source())) + ev->accept(); + break; } +#endif // QT_NO_GRAPHICSVIEW +#ifdef QT_KEYPAD_NAVIGATION + case QEvent::EnterEditFocus: + case QEvent::LeaveEditFocus: + if (QApplication::keypadNavigationEnabled()) + d->editFocusEvent(e); + break; +#endif + case QEvent::ShortcutOverride: + if (d->interactionFlags & Qt::TextEditable) { + QKeyEvent* ke = static_cast(e); + if (ke->modifiers() == Qt::NoModifier + || ke->modifiers() == Qt::ShiftModifier + || ke->modifiers() == Qt::KeypadModifier) { + if (ke->key() < Qt::Key_Escape) { + ke->accept(); + } else { + switch (ke->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Delete: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Backspace: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Tab: + ke->accept(); + default: + break; + } + } +#ifndef QT_NO_SHORTCUT + } else if (ke == QKeySequence::Copy + || ke == QKeySequence::Paste + || ke == QKeySequence::Cut + || ke == QKeySequence::Redo + || ke == QKeySequence::Undo + || ke == QKeySequence::MoveToNextWord + || ke == QKeySequence::MoveToPreviousWord + || ke == QKeySequence::MoveToStartOfDocument + || ke == QKeySequence::MoveToEndOfDocument + || ke == QKeySequence::SelectNextWord + || ke == QKeySequence::SelectPreviousWord + || ke == QKeySequence::SelectStartOfLine + || ke == QKeySequence::SelectEndOfLine + || ke == QKeySequence::SelectStartOfBlock + || ke == QKeySequence::SelectEndOfBlock + || ke == QKeySequence::SelectStartOfDocument + || ke == QKeySequence::SelectEndOfDocument + || ke == QKeySequence::SelectAll + ) { + ke->accept(); +#endif + } + } + break; + default: + break; + } +} + +bool QTextControl::event(QEvent *e) +{ + return QObject::event(e); +} + +void QTextControl::timerEvent(QTimerEvent *e) +{ + Q_D(QTextControl); + if (e->timerId() == d->cursorBlinkTimer.timerId()) { + d->cursorOn = !d->cursorOn; + + if (d->cursor.hasSelection()) + d->cursorOn &= (QApplication::style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected) + != 0); + + d->repaintCursor(); + } else if (e->timerId() == d->trippleClickTimer.timerId()) { + d->trippleClickTimer.stop(); + } +} + +void QTextControl::setPlainText(const QString &text) +{ + Q_D(QTextControl); + d->setContent(Qt::PlainText, text); +} + +void QTextControl::setHtml(const QString &text) +{ + Q_D(QTextControl); + d->setContent(Qt::RichText, text); +} + +void QTextControlPrivate::keyPressEvent(QKeyEvent *e) +{ + Q_Q(QTextControl); +#ifndef QT_NO_SHORTCUT + if (e == QKeySequence::SelectAll) { + e->accept(); + q->selectAll(); + return; + } +#ifndef QT_NO_CLIPBOARD + else if (e == QKeySequence::Copy) { + e->accept(); + q->copy(); + return; + } +#endif +#endif // QT_NO_SHORTCUT + + if (interactionFlags & Qt::TextSelectableByKeyboard + && cursorMoveKeyEvent(e)) + goto accept; + + if (interactionFlags & Qt::LinksAccessibleByKeyboard) { + if ((e->key() == Qt::Key_Return + || e->key() == Qt::Key_Enter +#ifdef QT_KEYPAD_NAVIGATION + || e->key() == Qt::Key_Select +#endif + ) + && cursor.hasSelection()) { + + e->accept(); + activateLinkUnderCursor(); + return; + } + } + + if (!(interactionFlags & Qt::TextEditable)) { + e->ignore(); + return; + } + + if (e->key() == Qt::Key_Direction_L || e->key() == Qt::Key_Direction_R) { + QTextBlockFormat fmt; + fmt.setLayoutDirection((e->key() == Qt::Key_Direction_L) ? Qt::LeftToRight : Qt::RightToLeft); + cursor.mergeBlockFormat(fmt); + goto accept; + } + + // schedule a repaint of the region of the cursor, as when we move it we + // want to make sure the old cursor disappears (not noticeable when moving + // only a few pixels but noticeable when jumping between cells in tables for + // example) + repaintSelection(); + + if (e->key() == Qt::Key_Backspace && !(e->modifiers() & ~Qt::ShiftModifier)) { + QTextBlockFormat blockFmt = cursor.blockFormat(); + QTextList *list = cursor.currentList(); + if (list && cursor.atBlockStart() && !cursor.hasSelection()) { + list->remove(cursor.block()); + } else if (cursor.atBlockStart() && blockFmt.indent() > 0) { + blockFmt.setIndent(blockFmt.indent() - 1); + cursor.setBlockFormat(blockFmt); + } else { + QTextCursor localCursor = cursor; + localCursor.deletePreviousChar(); + } + goto accept; + } +#ifndef QT_NO_SHORTCUT + else if (e == QKeySequence::InsertParagraphSeparator) { + cursor.insertBlock(); + e->accept(); + goto accept; + } else if (e == QKeySequence::InsertLineSeparator) { + cursor.insertText(QString(QChar::LineSeparator)); + e->accept(); + goto accept; + } +#endif + if (false) { + } +#ifndef QT_NO_SHORTCUT + else if (e == QKeySequence::Undo) { + q->undo(); + } + else if (e == QKeySequence::Redo) { + q->redo(); + } +#ifndef QT_NO_CLIPBOARD + else if (e == QKeySequence::Cut) { + q->cut(); + } + else if (e == QKeySequence::Paste) { + QClipboard::Mode mode = QClipboard::Clipboard; +#ifdef Q_WS_X11 + if (e->modifiers() == (Qt::CTRL | Qt::SHIFT) && e->key() == Qt::Key_Insert) + mode = QClipboard::Selection; +#endif + q->paste(mode); + } +#endif + else if (e == QKeySequence::Delete) { + QTextCursor localCursor = cursor; + localCursor.deleteChar(); + } + else if (e == QKeySequence::DeleteEndOfWord) { + if (!cursor.hasSelection()) + cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } + else if (e == QKeySequence::DeleteStartOfWord) { + if (!cursor.hasSelection()) + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } + else if (e == QKeySequence::DeleteEndOfLine) { + QTextBlock block = cursor.block(); + if (cursor.position() == block.position() + block.length() - 2) + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + else + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } +#endif // QT_NO_SHORTCUT + else { + goto process; + } + goto accept; + +process: + { + QString text = e->text(); + if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t'))) { + if (overwriteMode + // no need to call deleteChar() if we have a selection, insertText + // does it already + && !cursor.hasSelection() + && !cursor.atBlockEnd()) + cursor.deleteChar(); + + cursor.insertText(text); + selectionChanged(); + } else { + e->ignore(); + return; + } + } + + accept: + + e->accept(); + cursorOn = true; + + q->ensureCursorVisible(); + + updateCurrentCharFormat(); +} + +QVariant QTextControl::loadResource(int type, const QUrl &name) +{ +#ifdef QT_NO_TEXTEDIT + Q_UNUSED(type); + Q_UNUSED(name); +#else + if (QTextEdit *textEdit = qobject_cast(parent())) { + QUrl resolvedName = textEdit->d_func()->resolveUrl(name); + return textEdit->loadResource(type, resolvedName); + } +#endif + return QVariant(); +} + +void QTextControlPrivate::_q_updateBlock(const QTextBlock &block) +{ + Q_Q(QTextControl); + QRectF br = q->blockBoundingRect(block); + br.setRight(qreal(INT_MAX)); // the block might have shrunk + emit q->updateRequest(br); +} + +QRectF QTextControlPrivate::rectForPosition(int position) const +{ + Q_Q(const QTextControl); + const QTextBlock block = doc->findBlock(position); + if (!block.isValid()) + return QRectF(); + const QAbstractTextDocumentLayout *docLayout = doc->documentLayout(); + const QTextLayout *layout = block.layout(); + const QPointF layoutPos = q->blockBoundingRect(block).topLeft(); + int relativePos = position - block.position(); + if (preeditCursor != 0) { + int preeditPos = layout->preeditAreaPosition(); + if (relativePos == preeditPos) + relativePos += preeditCursor; + else if (relativePos > preeditPos) + relativePos += layout->preeditAreaText().length(); + } + QTextLine line = layout->lineForTextPosition(relativePos); + + int cursorWidth; + { + bool ok = false; +#ifndef QT_NO_PROPERTIES + cursorWidth = docLayout->property("cursorWidth").toInt(&ok); +#endif + if (!ok) + cursorWidth = 1; + } + + QRectF r; + + if (line.isValid()) { + qreal x = line.cursorToX(relativePos); + qreal w = 0; + if (overwriteMode) { + if (relativePos < line.textLength() - line.textStart()) + w = line.cursorToX(relativePos + 1) - x; + else + w = QFontMetrics(block.layout()->font()).width(QLatin1Char(' ')); // in sync with QTextLine::draw() + } + r = QRectF(layoutPos.x() + x, layoutPos.y() + line.y(), + cursorWidth + w, line.height()); + } else { + r = QRectF(layoutPos.x(), layoutPos.y(), cursorWidth, 10); // #### correct height + } + + return r; +} + +static inline bool firstFramePosLessThanCursorPos(QTextFrame *frame, int position) +{ + return frame->firstPosition() < position; +} + +static inline bool cursorPosLessThanLastFramePos(int position, QTextFrame *frame) +{ + return position < frame->lastPosition(); +} + +static QRectF boundingRectOfFloatsInSelection(const QTextCursor &cursor) +{ + QRectF r; + QTextFrame *frame = cursor.currentFrame(); + const QList children = frame->childFrames(); + + const QList::ConstIterator firstFrame = qLowerBound(children.constBegin(), children.constEnd(), + cursor.selectionStart(), firstFramePosLessThanCursorPos); + const QList::ConstIterator lastFrame = qUpperBound(children.constBegin(), children.constEnd(), + cursor.selectionEnd(), cursorPosLessThanLastFramePos); + for (QList::ConstIterator it = firstFrame; it != lastFrame; ++it) { + if ((*it)->frameFormat().position() != QTextFrameFormat::InFlow) + r |= frame->document()->documentLayout()->frameBoundingRect(*it); + } + return r; +} + +QRectF QTextControl::selectionRect(const QTextCursor &cursor) const +{ + Q_D(const QTextControl); + + QRectF r = d->rectForPosition(cursor.selectionStart()); + + if (cursor.hasComplexSelection() && cursor.currentTable()) { + QTextTable *table = cursor.currentTable(); + + r = d->doc->documentLayout()->frameBoundingRect(table); + /* + int firstRow, numRows, firstColumn, numColumns; + cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns); + + const QTextTableCell firstCell = table->cellAt(firstRow, firstColumn); + const QTextTableCell lastCell = table->cellAt(firstRow + numRows - 1, firstColumn + numColumns - 1); + + const QAbstractTextDocumentLayout * const layout = doc->documentLayout(); + + QRectF tableSelRect = layout->blockBoundingRect(firstCell.firstCursorPosition().block()); + + for (int col = firstColumn; col < firstColumn + numColumns; ++col) { + const QTextTableCell cell = table->cellAt(firstRow, col); + const qreal y = layout->blockBoundingRect(cell.firstCursorPosition().block()).top(); + + tableSelRect.setTop(qMin(tableSelRect.top(), y)); + } + + for (int row = firstRow; row < firstRow + numRows; ++row) { + const QTextTableCell cell = table->cellAt(row, firstColumn); + const qreal x = layout->blockBoundingRect(cell.firstCursorPosition().block()).left(); + + tableSelRect.setLeft(qMin(tableSelRect.left(), x)); + } + + for (int col = firstColumn; col < firstColumn + numColumns; ++col) { + const QTextTableCell cell = table->cellAt(firstRow + numRows - 1, col); + const qreal y = layout->blockBoundingRect(cell.lastCursorPosition().block()).bottom(); + + tableSelRect.setBottom(qMax(tableSelRect.bottom(), y)); + } + + for (int row = firstRow; row < firstRow + numRows; ++row) { + const QTextTableCell cell = table->cellAt(row, firstColumn + numColumns - 1); + const qreal x = layout->blockBoundingRect(cell.lastCursorPosition().block()).right(); + + tableSelRect.setRight(qMax(tableSelRect.right(), x)); + } + + r = tableSelRect.toRect(); + */ + } else if (cursor.hasSelection()) { + const int position = cursor.selectionStart(); + const int anchor = cursor.selectionEnd(); + const QTextBlock posBlock = d->doc->findBlock(position); + const QTextBlock anchorBlock = d->doc->findBlock(anchor); + if (posBlock == anchorBlock && posBlock.isValid() && posBlock.layout()->lineCount()) { + const QTextLine posLine = posBlock.layout()->lineForTextPosition(position - posBlock.position()); + const QTextLine anchorLine = anchorBlock.layout()->lineForTextPosition(anchor - anchorBlock.position()); + + const int firstLine = qMin(posLine.lineNumber(), anchorLine.lineNumber()); + const int lastLine = qMax(posLine.lineNumber(), anchorLine.lineNumber()); + const QTextLayout *layout = posBlock.layout(); + r = QRectF(); + for (int i = firstLine; i <= lastLine; ++i) { + r |= layout->lineAt(i).rect(); + r |= layout->lineAt(i).naturalTextRect(); // might be bigger in the case of wrap not enabled + } + r.translate(blockBoundingRect(posBlock).topLeft()); + } else { + QRectF anchorRect = d->rectForPosition(cursor.selectionEnd()); + r |= anchorRect; + r |= boundingRectOfFloatsInSelection(cursor); + QRectF frameRect(d->doc->documentLayout()->frameBoundingRect(cursor.currentFrame())); + r.setLeft(frameRect.left()); + r.setRight(frameRect.right()); + } + if (r.isValid()) + r.adjust(-1, -1, 1, 1); + } + + return r; +} + +QRectF QTextControl::selectionRect() const +{ + Q_D(const QTextControl); + return selectionRect(d->cursor); +} + +void QTextControlPrivate::mousePressEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, const QPoint &globalPos) +{ + Q_Q(QTextControl); + + if (sendMouseEventToInputContext( + e, QEvent::MouseButtonPress, button, pos, modifiers, buttons, globalPos)) { + return; + } + + if (interactionFlags & Qt::LinksAccessibleByMouse) { + anchorOnMousePress = q->anchorAt(pos); + + if (cursorIsFocusIndicator) { + cursorIsFocusIndicator = false; + repaintSelection(); + cursor.clearSelection(); + } + } + if (!(button & Qt::LeftButton) || + !((interactionFlags & Qt::TextSelectableByMouse) || (interactionFlags & Qt::TextEditable))) { + e->ignore(); + return; + } + + cursorIsFocusIndicator = false; + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + + mousePressed = (interactionFlags & Qt::TextSelectableByMouse); +#ifndef QT_NO_DRAGANDDROP + mightStartDrag = false; +#endif + + if (trippleClickTimer.isActive() + && ((pos - trippleClickPoint).toPoint().manhattanLength() < QApplication::startDragDistance())) { + + cursor.movePosition(QTextCursor::StartOfBlock); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + selectedBlockOnTrippleClick = cursor; + + anchorOnMousePress = QString(); + + trippleClickTimer.stop(); + } else { + int cursorPos = q->hitTest(pos, Qt::FuzzyHit); + if (cursorPos == -1) { + e->ignore(); + return; + } + + if (modifiers == Qt::ShiftModifier && (interactionFlags & Qt::TextSelectableByMouse)) { + if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) { + selectedWordOnDoubleClick = cursor; + selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor); + } + + if (selectedBlockOnTrippleClick.hasSelection()) + extendBlockwiseSelection(cursorPos); + else if (selectedWordOnDoubleClick.hasSelection()) + extendWordwiseSelection(cursorPos, pos.x()); + else if (!wordSelectionEnabled) + setCursorPosition(cursorPos, QTextCursor::KeepAnchor); + } else { + + if (dragEnabled + && cursor.hasSelection() + && !cursorIsFocusIndicator + && cursorPos >= cursor.selectionStart() + && cursorPos <= cursor.selectionEnd() + && q->hitTest(pos, Qt::ExactHit) != -1) { +#ifndef QT_NO_DRAGANDDROP + mightStartDrag = true; + dragStartPos = pos.toPoint(); +#endif + return; + } + + setCursorPosition(cursorPos); + } + } + + if (interactionFlags & Qt::TextEditable) { + q->ensureCursorVisible(); + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + _q_updateCurrentCharFormatAndSelection(); + } else { + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + selectionChanged(); + } + repaintOldAndNewSelection(oldSelection); + hadSelectionOnMousePress = cursor.hasSelection(); +} + +void QTextControlPrivate::mouseMoveEvent(QEvent *e, Qt::MouseButton button, const QPointF &mousePos, Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, const QPoint &globalPos) +{ + Q_Q(QTextControl); + + if (sendMouseEventToInputContext( + e, QEvent::MouseMove, button, mousePos, modifiers, buttons, globalPos)) { + return; + } + + if (interactionFlags & Qt::LinksAccessibleByMouse) { + QString anchor = q->anchorAt(mousePos); + if (anchor != highlightedAnchor) { + highlightedAnchor = anchor; + emit q->linkHovered(anchor); + } + } + + if (!(buttons & Qt::LeftButton)) + return; + + const bool editable = interactionFlags & Qt::TextEditable; + + if (!(mousePressed + || editable + || mightStartDrag + || selectedWordOnDoubleClick.hasSelection() + || selectedBlockOnTrippleClick.hasSelection())) + return; + + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + + if (mightStartDrag) { + if ((mousePos.toPoint() - dragStartPos).manhattanLength() > QApplication::startDragDistance()) + startDrag(); + return; + } + + if (!mousePressed) + return; + + const qreal mouseX = qreal(mousePos.x()); + + int newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit); + if (newCursorPos == -1) + return; + + if (wordSelectionEnabled && !selectedWordOnDoubleClick.hasSelection()) { + selectedWordOnDoubleClick = cursor; + selectedWordOnDoubleClick.select(QTextCursor::WordUnderCursor); + } + + if (selectedBlockOnTrippleClick.hasSelection()) + extendBlockwiseSelection(newCursorPos); + else if (selectedWordOnDoubleClick.hasSelection()) + extendWordwiseSelection(newCursorPos, mouseX); + else + setCursorPosition(newCursorPos, QTextCursor::KeepAnchor); + + if (interactionFlags & Qt::TextEditable) { + // don't call ensureVisible for the visible cursor to avoid jumping + // scrollbars. the autoscrolling ensures smooth scrolling if necessary. + //q->ensureCursorVisible(); + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + _q_updateCurrentCharFormatAndSelection(); +#ifndef QT_NO_IM + if (contextWidget) { + if (QInputContext *ic = inputContext()) { + ic->update(); + } + } +#endif //QT_NO_IM + } else { + //emit q->visibilityRequest(QRectF(mousePos, QSizeF(1, 1))); + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + } + selectionChanged(true); + repaintOldAndNewSelection(oldSelection); +} + +void QTextControlPrivate::mouseReleaseEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, const QPoint &globalPos) +{ + Q_Q(QTextControl); + + if (sendMouseEventToInputContext( + e, QEvent::MouseButtonRelease, button, pos, modifiers, buttons, globalPos)) { + return; + } + + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + +#ifndef QT_NO_DRAGANDDROP + if (mightStartDrag && (button & Qt::LeftButton)) { + mousePressed = false; + setCursorPosition(pos); + cursor.clearSelection(); + selectionChanged(); + } +#endif + if (mousePressed) { + mousePressed = false; +#ifndef QT_NO_CLIPBOARD + setClipboardSelection(); + selectionChanged(true); + } else if (button == Qt::MidButton + && (interactionFlags & Qt::TextEditable) + && QApplication::clipboard()->supportsSelection()) { + setCursorPosition(pos); + const QMimeData *md = QApplication::clipboard()->mimeData(QClipboard::Selection); + if (md) + q->insertFromMimeData(md); +#endif + } + + repaintOldAndNewSelection(oldSelection); + + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + + if (interactionFlags & Qt::LinksAccessibleByMouse) { + if (!(button & Qt::LeftButton)) + return; + + const QString anchor = q->anchorAt(pos); + + if (anchor.isEmpty()) + return; + + if (!cursor.hasSelection() + || (anchor == anchorOnMousePress && hadSelectionOnMousePress)) { + + const int anchorPos = q->hitTest(pos, Qt::ExactHit); + if (anchorPos != -1) { + cursor.setPosition(anchorPos); + + QString anchor = anchorOnMousePress; + anchorOnMousePress = QString(); + activateLinkUnderCursor(anchor); + } + } + } +} + +void QTextControlPrivate::mouseDoubleClickEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, const QPoint &globalPos) +{ + Q_Q(QTextControl); + + if (sendMouseEventToInputContext( + e, QEvent::MouseButtonDblClick, button, pos, modifiers, buttons, globalPos)) { + return; + } + + if (button != Qt::LeftButton + || !(interactionFlags & Qt::TextSelectableByMouse)) { + e->ignore(); + return; + } + +#ifndef QT_NO_DRAGANDDROP + mightStartDrag = false; +#endif + const QTextCursor oldSelection = cursor; + setCursorPosition(pos); + QTextLine line = currentTextLine(cursor); + bool doEmit = false; + if (line.isValid() && line.textLength()) { + cursor.select(QTextCursor::WordUnderCursor); + doEmit = true; + } + repaintOldAndNewSelection(oldSelection); + + cursorIsFocusIndicator = false; + selectedWordOnDoubleClick = cursor; + + trippleClickPoint = pos; + trippleClickTimer.start(QApplication::doubleClickInterval(), q); + if (doEmit) { + selectionChanged(); +#ifndef QT_NO_CLIPBOARD + setClipboardSelection(); +#endif + emit q->cursorPositionChanged(); + } +} + +bool QTextControlPrivate::sendMouseEventToInputContext( + QEvent *e, QEvent::Type eventType, Qt::MouseButton button, const QPointF &pos, + Qt::KeyboardModifiers modifiers, Qt::MouseButtons buttons, const QPoint &globalPos) +{ +#if !defined(QT_NO_IM) + Q_Q(QTextControl); + + QTextLayout *layout = cursor.block().layout(); + if (contextWidget && layout && !layout->preeditAreaText().isEmpty()) { + QInputContext *ctx = inputContext(); + int cursorPos = q->hitTest(pos, Qt::FuzzyHit) - cursor.position(); + + if (cursorPos < 0 || cursorPos > layout->preeditAreaText().length()) { + cursorPos = -1; + // don't send move events outside the preedit area + if (eventType == QEvent::MouseMove) + return true; + } + if (ctx) { + QMouseEvent ev(eventType, contextWidget->mapFromGlobal(globalPos), globalPos, + button, buttons, modifiers); + ctx->mouseHandler(cursorPos, &ev); + e->setAccepted(ev.isAccepted()); + } + if (!layout->preeditAreaText().isEmpty()) + return true; + } +#else + Q_UNUSED(e); + Q_UNUSED(eventType); + Q_UNUSED(button); + Q_UNUSED(pos); + Q_UNUSED(modifiers); + Q_UNUSED(buttons); + Q_UNUSED(globalPos); +#endif + return false; +} + +void QTextControlPrivate::contextMenuEvent(const QPoint &screenPos, const QPointF &docPos, QWidget *contextWidget) +{ +#ifdef QT_NO_CONTEXTMENU + Q_UNUSED(screenPos); + Q_UNUSED(docPos); + Q_UNUSED(contextWidget); +#else + Q_Q(QTextControl); + if (!hasFocus) + return; + QMenu *menu = q->createStandardContextMenu(docPos, contextWidget); + if (!menu) + return; + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->popup(screenPos); +#endif +} + +bool QTextControlPrivate::dragEnterEvent(QEvent *e, const QMimeData *mimeData) +{ + Q_Q(QTextControl); + if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) { + e->ignore(); + return false; + } + + dndFeedbackCursor = QTextCursor(); + + return true; // accept proposed action +} + +void QTextControlPrivate::dragLeaveEvent() +{ + Q_Q(QTextControl); + + const QRectF crect = q->cursorRect(dndFeedbackCursor); + dndFeedbackCursor = QTextCursor(); + + if (crect.isValid()) + emit q->updateRequest(crect); +} + +bool QTextControlPrivate::dragMoveEvent(QEvent *e, const QMimeData *mimeData, const QPointF &pos) +{ + Q_Q(QTextControl); + if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) { + e->ignore(); + return false; + } + + const int cursorPos = q->hitTest(pos, Qt::FuzzyHit); + if (cursorPos != -1) { + QRectF crect = q->cursorRect(dndFeedbackCursor); + if (crect.isValid()) + emit q->updateRequest(crect); + + dndFeedbackCursor = cursor; + dndFeedbackCursor.setPosition(cursorPos); + + crect = q->cursorRect(dndFeedbackCursor); + emit q->updateRequest(crect); + } + + return true; // accept proposed action +} + +bool QTextControlPrivate::dropEvent(const QMimeData *mimeData, const QPointF &pos, Qt::DropAction dropAction, QWidget *source) +{ + Q_Q(QTextControl); + dndFeedbackCursor = QTextCursor(); + + if (!(interactionFlags & Qt::TextEditable) || !q->canInsertFromMimeData(mimeData)) + return false; + + repaintSelection(); + + QTextCursor insertionCursor = q->cursorForPosition(pos); + insertionCursor.beginEditBlock(); + + if (dropAction == Qt::MoveAction && source == contextWidget) + cursor.removeSelectedText(); + + cursor = insertionCursor; + q->insertFromMimeData(mimeData); + insertionCursor.endEditBlock(); + q->ensureCursorVisible(); + return true; // accept proposed action +} + +void QTextControlPrivate::inputMethodEvent(QInputMethodEvent *e) +{ + Q_Q(QTextControl); + if (!(interactionFlags & Qt::TextEditable) || cursor.isNull()) { + e->ignore(); + return; + } + bool isGettingInput = !e->commitString().isEmpty() + || e->preeditString() != cursor.block().layout()->preeditAreaText() + || e->replacementLength() > 0; + + cursor.beginEditBlock(); + if (isGettingInput) { + cursor.removeSelectedText(); + } + + // insert commit string + if (!e->commitString().isEmpty() || e->replacementLength()) { + QTextCursor c = cursor; + c.setPosition(c.position() + e->replacementStart()); + c.setPosition(c.position() + e->replacementLength(), QTextCursor::KeepAnchor); + c.insertText(e->commitString()); + } + + for (int i = 0; i < e->attributes().size(); ++i) { + const QInputMethodEvent::Attribute &a = e->attributes().at(i); + if (a.type == QInputMethodEvent::Selection) { + QTextCursor oldCursor = cursor; + int blockStart = a.start + cursor.block().position(); + cursor.setPosition(blockStart, QTextCursor::MoveAnchor); + cursor.setPosition(blockStart + a.length, QTextCursor::KeepAnchor); + q->ensureCursorVisible(); + repaintOldAndNewSelection(oldCursor); + } + } + + QTextBlock block = cursor.block(); + QTextLayout *layout = block.layout(); + if (isGettingInput) + layout->setPreeditArea(cursor.position() - block.position(), e->preeditString()); + QList overrides; + const int oldPreeditCursor = preeditCursor; + preeditCursor = e->preeditString().length(); + hideCursor = false; + for (int i = 0; i < e->attributes().size(); ++i) { + const QInputMethodEvent::Attribute &a = e->attributes().at(i); + if (a.type == QInputMethodEvent::Cursor) { + preeditCursor = a.start; + hideCursor = !a.length; + } else if (a.type == QInputMethodEvent::TextFormat) { + QTextCharFormat f = qvariant_cast(a.value).toCharFormat(); + if (f.isValid()) { + QTextLayout::FormatRange o; + o.start = a.start + cursor.position() - block.position(); + o.length = a.length; + o.format = f; + overrides.append(o); + } + } + } + layout->setAdditionalFormats(overrides); + cursor.endEditBlock(); + if (cursor.d) + cursor.d->setX(); + if (oldPreeditCursor != preeditCursor) + emit q->microFocusChanged(); +} + +QVariant QTextControl::inputMethodQuery(Qt::InputMethodQuery property) const +{ + Q_D(const QTextControl); + QTextBlock block = d->cursor.block(); + switch(property) { + case Qt::ImMicroFocus: + return cursorRect(); + case Qt::ImFont: + return QVariant(d->cursor.charFormat().font()); + case Qt::ImCursorPosition: + return QVariant(d->cursor.position() - block.position()); + case Qt::ImSurroundingText: + return QVariant(block.text()); + case Qt::ImCurrentSelection: + return QVariant(d->cursor.selectedText()); + case Qt::ImMaximumTextLength: + return QVariant(); // No limit. + case Qt::ImAnchorPosition: + return QVariant(qBound(0, d->cursor.anchor() - block.position(), block.length())); + default: + return QVariant(); + } +} + +void QTextControl::setFocus(bool focus, Qt::FocusReason reason) +{ + QFocusEvent ev(focus ? QEvent::FocusIn : QEvent::FocusOut, + reason); + processEvent(&ev); +} + +void QTextControlPrivate::focusEvent(QFocusEvent *e) +{ + Q_Q(QTextControl); + emit q->updateRequest(q->selectionRect()); + if (e->gotFocus()) { +#ifdef QT_KEYPAD_NAVIGATION + if (!QApplication::keypadNavigationEnabled() || (hasEditFocus && (e->reason() == Qt::PopupFocusReason +#ifdef Q_OS_SYMBIAN + || e->reason() == Qt::ActiveWindowFocusReason +#endif + ))) { +#endif + cursorOn = (interactionFlags & Qt::TextSelectableByKeyboard); + if (interactionFlags & Qt::TextEditable) { + setBlinkingCursorEnabled(true); + } +#ifdef QT_KEYPAD_NAVIGATION + } +#endif + } else { + setBlinkingCursorEnabled(false); + + if (cursorIsFocusIndicator + && e->reason() != Qt::ActiveWindowFocusReason + && e->reason() != Qt::PopupFocusReason + && cursor.hasSelection()) { + cursor.clearSelection(); + } + } + hasFocus = e->gotFocus(); +} + +QString QTextControlPrivate::anchorForCursor(const QTextCursor &anchorCursor) const +{ + if (anchorCursor.hasSelection()) { + QTextCursor cursor = anchorCursor; + if (cursor.selectionStart() != cursor.position()) + cursor.setPosition(cursor.selectionStart()); + cursor.movePosition(QTextCursor::NextCharacter); + QTextCharFormat fmt = cursor.charFormat(); + if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) + return fmt.stringProperty(QTextFormat::AnchorHref); + } + return QString(); +} + +#ifdef QT_KEYPAD_NAVIGATION +void QTextControlPrivate::editFocusEvent(QEvent *e) +{ + Q_Q(QTextControl); + + if (QApplication::keypadNavigationEnabled()) { + if (e->type() == QEvent::EnterEditFocus && interactionFlags & Qt::TextEditable) { + const QTextCursor oldSelection = cursor; + const int oldCursorPos = cursor.position(); + const bool moved = cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); + q->ensureCursorVisible(); + if (moved) { + if (cursor.position() != oldCursorPos) + emit q->cursorPositionChanged(); + emit q->microFocusChanged(); + } + selectionChanged(); + repaintOldAndNewSelection(oldSelection); + + setBlinkingCursorEnabled(true); + } else + setBlinkingCursorEnabled(false); + } + + hasEditFocus = e->type() == QEvent::EnterEditFocus ? true : false; +} +#endif + +#ifndef QT_NO_CONTEXTMENU +QMenu *QTextControl::createStandardContextMenu(const QPointF &pos, QWidget *parent) +{ + Q_D(QTextControl); + + const bool showTextSelectionActions = d->interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); + + d->linkToCopy = QString(); + if (!pos.isNull()) + d->linkToCopy = anchorAt(pos); + + if (d->linkToCopy.isEmpty() && !showTextSelectionActions) + return 0; + + QMenu *menu = new QMenu(parent); + QAction *a; + + if (d->interactionFlags & Qt::TextEditable) { + a = menu->addAction(tr("&Undo") + ACCEL_KEY(QKeySequence::Undo), this, SLOT(undo())); + a->setEnabled(d->doc->isUndoAvailable()); + a = menu->addAction(tr("&Redo") + ACCEL_KEY(QKeySequence::Redo), this, SLOT(redo())); + a->setEnabled(d->doc->isRedoAvailable()); + menu->addSeparator(); + + a = menu->addAction(tr("Cu&t") + ACCEL_KEY(QKeySequence::Cut), this, SLOT(cut())); + a->setEnabled(d->cursor.hasSelection()); + } + + if (showTextSelectionActions) { + a = menu->addAction(tr("&Copy") + ACCEL_KEY(QKeySequence::Copy), this, SLOT(copy())); + a->setEnabled(d->cursor.hasSelection()); + } + + if ((d->interactionFlags & Qt::LinksAccessibleByKeyboard) + || (d->interactionFlags & Qt::LinksAccessibleByMouse)) { + + a = menu->addAction(tr("Copy &Link Location"), this, SLOT(_q_copyLink())); + a->setEnabled(!d->linkToCopy.isEmpty()); + } + + if (d->interactionFlags & Qt::TextEditable) { +#if !defined(QT_NO_CLIPBOARD) + a = menu->addAction(tr("&Paste") + ACCEL_KEY(QKeySequence::Paste), this, SLOT(paste())); + a->setEnabled(canPaste()); +#endif + a = menu->addAction(tr("Delete"), this, SLOT(_q_deleteSelected())); + a->setEnabled(d->cursor.hasSelection()); + } + + + if (showTextSelectionActions) { + menu->addSeparator(); + a = menu->addAction(tr("Select All") + ACCEL_KEY(QKeySequence::SelectAll), this, SLOT(selectAll())); + a->setEnabled(!d->doc->isEmpty()); + } + +#if !defined(QT_NO_IM) + if (d->contextWidget) { + QInputContext *qic = d->inputContext(); + if (qic) { + QList imActions = qic->actions(); + for (int i = 0; i < imActions.size(); ++i) + menu->addAction(imActions.at(i)); + } + } +#endif + +#if defined(Q_WS_WIN) || defined(Q_WS_X11) + if ((d->interactionFlags & Qt::TextEditable) && qt_use_rtl_extensions) { +#else + if (d->interactionFlags & Qt::TextEditable) { +#endif + menu->addSeparator(); + QUnicodeControlCharacterMenu *ctrlCharacterMenu = new QUnicodeControlCharacterMenu(this, menu); + menu->addMenu(ctrlCharacterMenu); + } + + return menu; +} +#endif // QT_NO_CONTEXTMENU + +QTextCursor QTextControl::cursorForPosition(const QPointF &pos) const +{ + Q_D(const QTextControl); + int cursorPos = hitTest(pos, Qt::FuzzyHit); + if (cursorPos == -1) + cursorPos = 0; + QTextCursor c(d->doc); + c.setPosition(cursorPos); + return c; +} + +QRectF QTextControl::cursorRect(const QTextCursor &cursor) const +{ + Q_D(const QTextControl); + if (cursor.isNull()) + return QRectF(); + + return d->rectForPosition(cursor.position()); +} + +QRectF QTextControl::cursorRect() const +{ + Q_D(const QTextControl); + return cursorRect(d->cursor); +} + +QRectF QTextControlPrivate::cursorRectPlusUnicodeDirectionMarkers(const QTextCursor &cursor) const +{ + if (cursor.isNull()) + return QRectF(); + + return rectForPosition(cursor.position()).adjusted(-4, 0, 4, 0); +} + +QString QTextControl::anchorAt(const QPointF &pos) const +{ + Q_D(const QTextControl); + return d->doc->documentLayout()->anchorAt(pos); +} + +QString QTextControl::anchorAtCursor() const +{ + Q_D(const QTextControl); + + return d->anchorForCursor(d->cursor); +} + +bool QTextControl::overwriteMode() const +{ + Q_D(const QTextControl); + return d->overwriteMode; +} + +void QTextControl::setOverwriteMode(bool overwrite) +{ + Q_D(QTextControl); + d->overwriteMode = overwrite; +} + +int QTextControl::cursorWidth() const +{ +#ifndef QT_NO_PROPERTIES + Q_D(const QTextControl); + return d->doc->documentLayout()->property("cursorWidth").toInt(); +#else + return 1; +#endif +} + +void QTextControl::setCursorWidth(int width) +{ + Q_D(QTextControl); +#ifdef QT_NO_PROPERTIES + Q_UNUSED(width); +#else + if (width == -1) + width = QApplication::style()->pixelMetric(QStyle::PM_TextCursorWidth); + d->doc->documentLayout()->setProperty("cursorWidth", width); +#endif + d->repaintCursor(); +} + +bool QTextControl::acceptRichText() const +{ + Q_D(const QTextControl); + return d->acceptRichText; +} + +void QTextControl::setAcceptRichText(bool accept) +{ + Q_D(QTextControl); + d->acceptRichText = accept; +} + +#ifndef QT_NO_TEXTEDIT + +void QTextControl::setExtraSelections(const QList &selections) +{ + Q_D(QTextControl); + + QHash hash; + for (int i = 0; i < d->extraSelections.count(); ++i) { + const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(i); + hash.insertMulti(esel.cursor.anchor(), i); + } + + for (int i = 0; i < selections.count(); ++i) { + const QTextEdit::ExtraSelection &sel = selections.at(i); + QHash::iterator it = hash.find(sel.cursor.anchor()); + if (it != hash.end()) { + const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(it.value()); + if (esel.cursor.position() == sel.cursor.position() + && esel.format == sel.format) { + hash.erase(it); + continue; + } + } + QRectF r = selectionRect(sel.cursor); + if (sel.format.boolProperty(QTextFormat::FullWidthSelection)) { + r.setLeft(0); + r.setWidth(qreal(INT_MAX)); + } + emit updateRequest(r); + } + + for (QHash::iterator it = hash.begin(); it != hash.end(); ++it) { + const QAbstractTextDocumentLayout::Selection &esel = d->extraSelections.at(it.value()); + QRectF r = selectionRect(esel.cursor); + if (esel.format.boolProperty(QTextFormat::FullWidthSelection)) { + r.setLeft(0); + r.setWidth(qreal(INT_MAX)); + } + emit updateRequest(r); + } + + d->extraSelections.resize(selections.count()); + for (int i = 0; i < selections.count(); ++i) { + d->extraSelections[i].cursor = selections.at(i).cursor; + d->extraSelections[i].format = selections.at(i).format; + } +} + +QList QTextControl::extraSelections() const +{ + Q_D(const QTextControl); + QList selections; + for (int i = 0; i < d->extraSelections.count(); ++i) { + QTextEdit::ExtraSelection sel; + sel.cursor = d->extraSelections.at(i).cursor; + sel.format = d->extraSelections.at(i).format; + selections.append(sel); + } + return selections; +} + +#endif // QT_NO_TEXTEDIT + +void QTextControl::setTextWidth(qreal width) +{ + Q_D(QTextControl); + d->doc->setTextWidth(width); +} + +qreal QTextControl::textWidth() const +{ + Q_D(const QTextControl); + return d->doc->textWidth(); +} + +QSizeF QTextControl::size() const +{ + Q_D(const QTextControl); + return d->doc->size(); +} + +void QTextControl::setOpenExternalLinks(bool open) +{ + Q_D(QTextControl); + d->openExternalLinks = open; +} + +bool QTextControl::openExternalLinks() const +{ + Q_D(const QTextControl); + return d->openExternalLinks; +} + +bool QTextControl::ignoreUnusedNavigationEvents() const +{ + Q_D(const QTextControl); + return d->ignoreUnusedNavigationEvents; +} + +void QTextControl::setIgnoreUnusedNavigationEvents(bool ignore) +{ + Q_D(QTextControl); + d->ignoreUnusedNavigationEvents = ignore; +} + +void QTextControl::moveCursor(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode) +{ + Q_D(QTextControl); + const QTextCursor oldSelection = d->cursor; + const bool moved = d->cursor.movePosition(op, mode); + d->_q_updateCurrentCharFormatAndSelection(); + ensureCursorVisible(); + d->repaintOldAndNewSelection(oldSelection); + if (moved) + emit cursorPositionChanged(); +} + +bool QTextControl::canPaste() const +{ +#ifndef QT_NO_CLIPBOARD + Q_D(const QTextControl); + if (d->interactionFlags & Qt::TextEditable) { + const QMimeData *md = QApplication::clipboard()->mimeData(); + return md && canInsertFromMimeData(md); + } +#endif + return false; +} + +void QTextControl::setCursorIsFocusIndicator(bool b) +{ + Q_D(QTextControl); + d->cursorIsFocusIndicator = b; + d->repaintCursor(); +} + +bool QTextControl::cursorIsFocusIndicator() const +{ + Q_D(const QTextControl); + return d->cursorIsFocusIndicator; +} + + +void QTextControl::setDragEnabled(bool enabled) +{ + Q_D(QTextControl); + d->dragEnabled = enabled; +} + +bool QTextControl::isDragEnabled() const +{ + Q_D(const QTextControl); + return d->dragEnabled; +} + +void QTextControl::setWordSelectionEnabled(bool enabled) +{ + Q_D(QTextControl); + d->wordSelectionEnabled = enabled; +} + +bool QTextControl::isWordSelectionEnabled() const +{ + Q_D(const QTextControl); + return d->wordSelectionEnabled; +} + +#ifndef QT_NO_PRINTER +void QTextControl::print(QPrinter *printer) const +{ +#ifndef QT_NO_PRINTER + Q_D(const QTextControl); + if (!printer || !printer->isValid()) + return; + QTextDocument *tempDoc = 0; + const QTextDocument *doc = d->doc; + if (printer->printRange() == QPrinter::Selection) { + if (!d->cursor.hasSelection()) + return; + tempDoc = new QTextDocument(const_cast(doc)); + tempDoc->setMetaInformation(QTextDocument::DocumentTitle, doc->metaInformation(QTextDocument::DocumentTitle)); + tempDoc->setPageSize(doc->pageSize()); + tempDoc->setDefaultFont(doc->defaultFont()); + tempDoc->setUseDesignMetrics(doc->useDesignMetrics()); + QTextCursor(tempDoc).insertFragment(d->cursor.selection()); + doc = tempDoc; + + // copy the custom object handlers + doc->documentLayout()->d_func()->handlers = d->doc->documentLayout()->d_func()->handlers; + } + doc->print(printer); + delete tempDoc; +#endif +} +#endif // QT_NO_PRINTER + +QMimeData *QTextControl::createMimeDataFromSelection() const +{ + Q_D(const QTextControl); + const QTextDocumentFragment fragment(d->cursor); + return new QTextEditMimeData(fragment); +} + +bool QTextControl::canInsertFromMimeData(const QMimeData *source) const +{ + Q_D(const QTextControl); + if (d->acceptRichText) + return (source->hasText() && !source->text().isEmpty()) + || source->hasHtml() + || source->hasFormat(QLatin1String("application/x-qrichtext")) + || source->hasFormat(QLatin1String("application/x-qt-richtext")); + else + return source->hasText() && !source->text().isEmpty(); +} + +void QTextControl::insertFromMimeData(const QMimeData *source) +{ + Q_D(QTextControl); + if (!(d->interactionFlags & Qt::TextEditable) || !source) + return; + + bool hasData = false; + QTextDocumentFragment fragment; +#ifndef QT_NO_TEXTHTMLPARSER + if (source->hasFormat(QLatin1String("application/x-qrichtext")) && d->acceptRichText) { + // x-qrichtext is always UTF-8 (taken from Qt3 since we don't use it anymore). + QString richtext = QString::fromUtf8(source->data(QLatin1String("application/x-qrichtext"))); + richtext.prepend(QLatin1String("")); + fragment = QTextDocumentFragment::fromHtml(richtext, d->doc); + hasData = true; + } else if (source->hasHtml() && d->acceptRichText) { + fragment = QTextDocumentFragment::fromHtml(source->html(), d->doc); + hasData = true; + } else { + QString text = source->text(); + if (!text.isNull()) { + fragment = QTextDocumentFragment::fromPlainText(text); + hasData = true; + } + } +#else + fragment = QTextDocumentFragment::fromPlainText(source->text()); +#endif // QT_NO_TEXTHTMLPARSER + + if (hasData) + d->cursor.insertFragment(fragment); + ensureCursorVisible(); +} + +bool QTextControl::findNextPrevAnchor(const QTextCursor &startCursor, bool next, QTextCursor &newAnchor) +{ + Q_D(QTextControl); + + int anchorStart = -1; + QString anchorHref; + int anchorEnd = -1; + + if (next) { + const int startPos = startCursor.selectionEnd(); + + QTextBlock block = d->doc->findBlock(startPos); + QTextBlock::Iterator it = block.begin(); + + while (!it.atEnd() && it.fragment().position() < startPos) + ++it; + + while (block.isValid()) { + anchorStart = -1; + + // find next anchor + for (; !it.atEnd(); ++it) { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) { + anchorStart = fragment.position(); + anchorHref = fmt.anchorHref(); + break; + } + } + + if (anchorStart != -1) { + anchorEnd = -1; + + // find next non-anchor fragment + for (; !it.atEnd(); ++it) { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (!fmt.isAnchor() || fmt.anchorHref() != anchorHref) { + anchorEnd = fragment.position(); + break; + } + } + + if (anchorEnd == -1) + anchorEnd = block.position() + block.length() - 1; + + // make found selection + break; + } + + block = block.next(); + it = block.begin(); + } + } else { + int startPos = startCursor.selectionStart(); + if (startPos > 0) + --startPos; + + QTextBlock block = d->doc->findBlock(startPos); + QTextBlock::Iterator blockStart = block.begin(); + QTextBlock::Iterator it = block.end(); + + if (startPos == block.position()) { + it = block.begin(); + } else { + do { + if (it == blockStart) { + it = QTextBlock::Iterator(); + block = QTextBlock(); + } else { + --it; + } + } while (!it.atEnd() && it.fragment().position() + it.fragment().length() - 1 > startPos); + } + + while (block.isValid()) { + anchorStart = -1; + + if (!it.atEnd()) { + do { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) { + anchorStart = fragment.position() + fragment.length(); + anchorHref = fmt.anchorHref(); + break; + } + + if (it == blockStart) + it = QTextBlock::Iterator(); + else + --it; + } while (!it.atEnd()); + } + + if (anchorStart != -1 && !it.atEnd()) { + anchorEnd = -1; + + do { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (!fmt.isAnchor() || fmt.anchorHref() != anchorHref) { + anchorEnd = fragment.position() + fragment.length(); + break; + } + + if (it == blockStart) + it = QTextBlock::Iterator(); + else + --it; + } while (!it.atEnd()); + + if (anchorEnd == -1) + anchorEnd = qMax(0, block.position()); + + break; + } + + block = block.previous(); + it = block.end(); + if (it != block.begin()) + --it; + blockStart = block.begin(); + } + + } + + if (anchorStart != -1 && anchorEnd != -1) { + newAnchor = d->cursor; + newAnchor.setPosition(anchorStart); + newAnchor.setPosition(anchorEnd, QTextCursor::KeepAnchor); + return true; + } + + return false; +} + +void QTextControlPrivate::activateLinkUnderCursor(QString href) +{ + QTextCursor oldCursor = cursor; + + if (href.isEmpty()) { + QTextCursor tmp = cursor; + if (tmp.selectionStart() != tmp.position()) + tmp.setPosition(tmp.selectionStart()); + tmp.movePosition(QTextCursor::NextCharacter); + href = tmp.charFormat().anchorHref(); + } + if (href.isEmpty()) + return; + + if (!cursor.hasSelection()) { + QTextBlock block = cursor.block(); + const int cursorPos = cursor.position(); + + QTextBlock::Iterator it = block.begin(); + QTextBlock::Iterator linkFragment; + + for (; !it.atEnd(); ++it) { + QTextFragment fragment = it.fragment(); + const int fragmentPos = fragment.position(); + if (fragmentPos <= cursorPos && + fragmentPos + fragment.length() > cursorPos) { + linkFragment = it; + break; + } + } + + if (!linkFragment.atEnd()) { + it = linkFragment; + cursor.setPosition(it.fragment().position()); + if (it != block.begin()) { + do { + --it; + QTextFragment fragment = it.fragment(); + if (fragment.charFormat().anchorHref() != href) + break; + cursor.setPosition(fragment.position()); + } while (it != block.begin()); + } + + for (it = linkFragment; !it.atEnd(); ++it) { + QTextFragment fragment = it.fragment(); + if (fragment.charFormat().anchorHref() != href) + break; + cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor); + } + } + } + + if (hasFocus) { + cursorIsFocusIndicator = true; + } else { + cursorIsFocusIndicator = false; + cursor.clearSelection(); + } + repaintOldAndNewSelection(oldCursor); + +#ifndef QT_NO_DESKTOPSERVICES + if (openExternalLinks) + QDesktopServices::openUrl(href); + else +#endif + emit q_func()->linkActivated(href); +} + +#ifndef QT_NO_TOOLTIP +void QTextControlPrivate::showToolTip(const QPoint &globalPos, const QPointF &pos, QWidget *contextWidget) +{ + const QString toolTip = q_func()->cursorForPosition(pos).charFormat().toolTip(); + if (toolTip.isEmpty()) + return; + QToolTip::showText(globalPos, toolTip, contextWidget); +} +#endif // QT_NO_TOOLTIP + +bool QTextControl::setFocusToNextOrPreviousAnchor(bool next) +{ + Q_D(QTextControl); + + if (!(d->interactionFlags & Qt::LinksAccessibleByKeyboard)) + return false; + + QRectF crect = selectionRect(); + emit updateRequest(crect); + + // If we don't have a current anchor, we start from the start/end + if (!d->cursor.hasSelection()) { + d->cursor = QTextCursor(d->doc); + if (next) + d->cursor.movePosition(QTextCursor::Start); + else + d->cursor.movePosition(QTextCursor::End); + } + + QTextCursor newAnchor; + if (findNextPrevAnchor(d->cursor, next, newAnchor)) { + d->cursor = newAnchor; + d->cursorIsFocusIndicator = true; + } else { + d->cursor.clearSelection(); + } + + if (d->cursor.hasSelection()) { + crect = selectionRect(); + emit updateRequest(crect); + emit visibilityRequest(crect); + return true; + } else { + return false; + } +} + +bool QTextControl::setFocusToAnchor(const QTextCursor &newCursor) +{ + Q_D(QTextControl); + + if (!(d->interactionFlags & Qt::LinksAccessibleByKeyboard)) + return false; + + // Verify that this is an anchor. + const QString anchorHref = d->anchorForCursor(newCursor); + if (anchorHref.isEmpty()) + return false; + + // and process it + QRectF crect = selectionRect(); + emit updateRequest(crect); + + d->cursor.setPosition(newCursor.selectionStart()); + d->cursor.setPosition(newCursor.selectionEnd(), QTextCursor::KeepAnchor); + d->cursorIsFocusIndicator = true; + + crect = selectionRect(); + emit updateRequest(crect); + emit visibilityRequest(crect); + return true; +} + +void QTextControl::setTextInteractionFlags(Qt::TextInteractionFlags flags) +{ + Q_D(QTextControl); + if (flags == d->interactionFlags) + return; + d->interactionFlags = flags; + + if (d->hasFocus) + d->setBlinkingCursorEnabled(flags & Qt::TextEditable); +} + +Qt::TextInteractionFlags QTextControl::textInteractionFlags() const +{ + Q_D(const QTextControl); + return d->interactionFlags; +} + +void QTextControl::mergeCurrentCharFormat(const QTextCharFormat &modifier) +{ + Q_D(QTextControl); + d->cursor.mergeCharFormat(modifier); + d->updateCurrentCharFormat(); +} + +void QTextControl::setCurrentCharFormat(const QTextCharFormat &format) +{ + Q_D(QTextControl); + d->cursor.setCharFormat(format); + d->updateCurrentCharFormat(); +} + +QTextCharFormat QTextControl::currentCharFormat() const +{ + Q_D(const QTextControl); + return d->cursor.charFormat(); +} + +void QTextControl::insertPlainText(const QString &text) +{ + Q_D(QTextControl); + d->cursor.insertText(text); +} + +#ifndef QT_NO_TEXTHTMLPARSER +void QTextControl::insertHtml(const QString &text) +{ + Q_D(QTextControl); + d->cursor.insertHtml(text); +} +#endif // QT_NO_TEXTHTMLPARSER + +QPointF QTextControl::anchorPosition(const QString &name) const +{ + Q_D(const QTextControl); + if (name.isEmpty()) + return QPointF(); + + QRectF r; + for (QTextBlock block = d->doc->begin(); block.isValid(); block = block.next()) { + QTextCharFormat format = block.charFormat(); + if (format.isAnchor() && format.anchorNames().contains(name)) { + r = d->rectForPosition(block.position()); + break; + } + + for (QTextBlock::Iterator it = block.begin(); !it.atEnd(); ++it) { + QTextFragment fragment = it.fragment(); + format = fragment.charFormat(); + if (format.isAnchor() && format.anchorNames().contains(name)) { + r = d->rectForPosition(fragment.position()); + block = QTextBlock(); + break; + } + } + } + if (!r.isValid()) + return QPointF(); + return QPointF(0, r.top()); +} + +void QTextControl::adjustSize() +{ + Q_D(QTextControl); + d->doc->adjustSize(); +} + +bool QTextControl::find(const QString &exp, QTextDocument::FindFlags options) +{ + Q_D(QTextControl); + QTextCursor search = d->doc->find(exp, d->cursor, options); + if (search.isNull()) + return false; + + setTextCursor(search); + return true; +} + + + +void QTextControlPrivate::append(const QString &text, Qt::TextFormat format) +{ + QTextCursor tmp(doc); + tmp.beginEditBlock(); + tmp.movePosition(QTextCursor::End); + + if (!doc->isEmpty()) + tmp.insertBlock(cursor.blockFormat(), cursor.charFormat()); + else + tmp.setCharFormat(cursor.charFormat()); + + // preserve the char format + QTextCharFormat oldCharFormat = cursor.charFormat(); + +#ifndef QT_NO_TEXTHTMLPARSER + if (format == Qt::RichText || (format == Qt::AutoText && Qt::mightBeRichText(text))) { + tmp.insertHtml(text); + } else { + tmp.insertText(text); + } +#else + tmp.insertText(text); +#endif // QT_NO_TEXTHTMLPARSER + if (!cursor.hasSelection()) + cursor.setCharFormat(oldCharFormat); + + tmp.endEditBlock(); +} + +void QTextControl::append(const QString &text) +{ + Q_D(QTextControl); + d->append(text, Qt::AutoText); +} + +void QTextControl::appendHtml(const QString &html) +{ + Q_D(QTextControl); + d->append(html, Qt::RichText); +} + +void QTextControl::appendPlainText(const QString &text) +{ + Q_D(QTextControl); + d->append(text, Qt::PlainText); +} + + +void QTextControl::ensureCursorVisible() +{ + Q_D(QTextControl); + QRectF crect = d->rectForPosition(d->cursor.position()).adjusted(-5, 0, 5, 0); + emit visibilityRequest(crect); + emit microFocusChanged(); +} + +QPalette QTextControl::palette() const +{ + Q_D(const QTextControl); + return d->palette; +} + +void QTextControl::setPalette(const QPalette &pal) +{ + Q_D(QTextControl); + d->palette = pal; +} + +QAbstractTextDocumentLayout::PaintContext QTextControl::getPaintContext(QWidget *widget) const +{ + Q_D(const QTextControl); + + QAbstractTextDocumentLayout::PaintContext ctx; + + ctx.selections = d->extraSelections; + ctx.palette = d->palette; + if (d->cursorOn && d->isEnabled) { + if (d->hideCursor) + ctx.cursorPosition = -1; + else if (d->preeditCursor != 0) + ctx.cursorPosition = - (d->preeditCursor + 2); + else + ctx.cursorPosition = d->cursor.position(); + } + + if (!d->dndFeedbackCursor.isNull()) + ctx.cursorPosition = d->dndFeedbackCursor.position(); +#ifdef QT_KEYPAD_NAVIGATION + if (!QApplication::keypadNavigationEnabled() || d->hasEditFocus) +#endif + if (d->cursor.hasSelection()) { + QAbstractTextDocumentLayout::Selection selection; + selection.cursor = d->cursor; + if (d->cursorIsFocusIndicator) { + QStyleOption opt; + opt.palette = ctx.palette; + QStyleHintReturnVariant ret; + QStyle *style = QApplication::style(); + if (widget) + style = widget->style(); + style->styleHint(QStyle::SH_TextControl_FocusIndicatorTextCharFormat, &opt, widget, &ret); + selection.format = qvariant_cast(ret.variant).toCharFormat(); + } else { + QPalette::ColorGroup cg = d->hasFocus ? QPalette::Active : QPalette::Inactive; + selection.format.setBackground(ctx.palette.brush(cg, QPalette::Highlight)); + selection.format.setForeground(ctx.palette.brush(cg, QPalette::HighlightedText)); + QStyleOption opt; + QStyle *style = QApplication::style(); + if (widget) { + opt.initFrom(widget); + style = widget->style(); + } + if (style->styleHint(QStyle::SH_RichText_FullWidthSelection, &opt, widget)) + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + } + ctx.selections.append(selection); + } + + return ctx; +} + +void QTextControl::drawContents(QPainter *p, const QRectF &rect, QWidget *widget) +{ + Q_D(QTextControl); + p->save(); + QAbstractTextDocumentLayout::PaintContext ctx = getPaintContext(widget); + if (rect.isValid()) + p->setClipRect(rect, Qt::IntersectClip); + ctx.clip = rect; + + d->doc->documentLayout()->draw(p, ctx); + p->restore(); +} + +void QTextControlPrivate::_q_copyLink() +{ +#ifndef QT_NO_CLIPBOARD + QMimeData *md = new QMimeData; + md->setText(linkToCopy); + QApplication::clipboard()->setMimeData(md); +#endif +} + +QInputContext *QTextControlPrivate::inputContext() +{ + QInputContext *ctx = contextWidget->inputContext(); + if (!ctx && contextWidget->parentWidget()) + ctx = contextWidget->parentWidget()->inputContext(); + return ctx; +} + +int QTextControl::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const +{ + Q_D(const QTextControl); + return d->doc->documentLayout()->hitTest(point, accuracy); +} + +QRectF QTextControl::blockBoundingRect(const QTextBlock &block) const +{ + Q_D(const QTextControl); + return d->doc->documentLayout()->blockBoundingRect(block); +} + +#ifndef QT_NO_CONTEXTMENU +#define NUM_CONTROL_CHARACTERS 10 +const struct QUnicodeControlCharacter { + const char *text; + ushort character; +} qt_controlCharacters[NUM_CONTROL_CHARACTERS] = { + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRM Left-to-right mark"), 0x200e }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLM Right-to-left mark"), 0x200f }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWJ Zero width joiner"), 0x200d }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWNJ Zero width non-joiner"), 0x200c }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "ZWSP Zero width space"), 0x200b }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRE Start of left-to-right embedding"), 0x202a }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLE Start of right-to-left embedding"), 0x202b }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "LRO Start of left-to-right override"), 0x202d }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "RLO Start of right-to-left override"), 0x202e }, + { QT_TRANSLATE_NOOP("QUnicodeControlCharacterMenu", "PDF Pop directional formatting"), 0x202c }, +}; + +QUnicodeControlCharacterMenu::QUnicodeControlCharacterMenu(QObject *_editWidget, QWidget *parent) + : QMenu(parent), editWidget(_editWidget) +{ + setTitle(tr("Insert Unicode control character")); + for (int i = 0; i < NUM_CONTROL_CHARACTERS; ++i) { + addAction(tr(qt_controlCharacters[i].text), this, SLOT(menuActionTriggered())); + } +} + +void QUnicodeControlCharacterMenu::menuActionTriggered() +{ + QAction *a = qobject_cast(sender()); + int idx = actions().indexOf(a); + if (idx < 0 || idx >= NUM_CONTROL_CHARACTERS) + return; + QChar c(qt_controlCharacters[idx].character); + QString str(c); + +#ifndef QT_NO_TEXTEDIT + if (QTextEdit *edit = qobject_cast(editWidget)) { + edit->insertPlainText(str); + return; + } +#endif + if (QTextControl *control = qobject_cast(editWidget)) { + control->insertPlainText(str); + } +#ifndef QT_NO_LINEEDIT + if (QLineEdit *edit = qobject_cast(editWidget)) { + edit->insert(str); + return; + } +#endif +} +#endif // QT_NO_CONTEXTMENU + +QStringList QTextEditMimeData::formats() const +{ + if (!fragment.isEmpty()) + return QStringList() << QString::fromLatin1("text/plain") << QString::fromLatin1("text/html") +#ifndef QT_NO_TEXTODFWRITER + << QString::fromLatin1("application/vnd.oasis.opendocument.text") +#endif + ; + else + return QMimeData::formats(); +} + +QVariant QTextEditMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const +{ + if (!fragment.isEmpty()) + setup(); + return QMimeData::retrieveData(mimeType, type); +} + +void QTextEditMimeData::setup() const +{ + QTextEditMimeData *that = const_cast(this); +#ifndef QT_NO_TEXTHTMLPARSER + that->setData(QLatin1String("text/html"), fragment.toHtml("utf-8").toUtf8()); +#endif +#ifndef QT_NO_TEXTODFWRITER + { + QBuffer buffer; + QTextDocumentWriter writer(&buffer, "ODF"); + writer.write(fragment); + buffer.close(); + that->setData(QLatin1String("application/vnd.oasis.opendocument.text"), buffer.data()); + } +#endif + that->setText(fragment.toPlainText()); + fragment = QTextDocumentFragment(); +} + +QT_END_NAMESPACE + +#include "moc_qtextcontrol_p.cpp" + +#endif // QT_NO_TEXTCONTROL diff --git a/src/gui/text/qtextcontrol_p.h b/src/gui/text/qtextcontrol_p.h new file mode 100644 index 0000000000..31fa843a3a --- /dev/null +++ b/src/gui/text/qtextcontrol_p.h @@ -0,0 +1,312 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTCONTROL_P_H +#define QTEXTCONTROL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef QT3_SUPPORT +#include +#include +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QStyleSheet; +class QTextDocument; +class QMenu; +class QTextControlPrivate; +class QMimeData; +class QAbstractScrollArea; +class QEvent; +class QTimerEvent; + +class Q_GUI_EXPORT QTextControl : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QTextControl) +#ifndef QT_NO_TEXTHTMLPARSER + Q_PROPERTY(QString html READ toHtml WRITE setHtml NOTIFY textChanged USER true) +#endif + Q_PROPERTY(bool overwriteMode READ overwriteMode WRITE setOverwriteMode) + Q_PROPERTY(bool acceptRichText READ acceptRichText WRITE setAcceptRichText) + Q_PROPERTY(int cursorWidth READ cursorWidth WRITE setCursorWidth) + Q_PROPERTY(Qt::TextInteractionFlags textInteractionFlags READ textInteractionFlags WRITE setTextInteractionFlags) + Q_PROPERTY(bool openExternalLinks READ openExternalLinks WRITE setOpenExternalLinks) + Q_PROPERTY(bool ignoreUnusedNavigationEvents READ ignoreUnusedNavigationEvents WRITE setIgnoreUnusedNavigationEvents) +public: + explicit QTextControl(QObject *parent = 0); + explicit QTextControl(const QString &text, QObject *parent = 0); + explicit QTextControl(QTextDocument *doc, QObject *parent = 0); + virtual ~QTextControl(); + + void setDocument(QTextDocument *document); + QTextDocument *document() const; + + void setTextCursor(const QTextCursor &cursor); + QTextCursor textCursor() const; + + void setTextInteractionFlags(Qt::TextInteractionFlags flags); + Qt::TextInteractionFlags textInteractionFlags() const; + + void mergeCurrentCharFormat(const QTextCharFormat &modifier); + + void setCurrentCharFormat(const QTextCharFormat &format); + QTextCharFormat currentCharFormat() const; + + bool find(const QString &exp, QTextDocument::FindFlags options = 0); + + inline QString toPlainText() const + { return document()->toPlainText(); } +#ifndef QT_NO_TEXTHTMLPARSER + inline QString toHtml() const + { return document()->toHtml(); } +#endif + + virtual void ensureCursorVisible(); + + virtual QVariant loadResource(int type, const QUrl &name); +#ifndef QT_NO_CONTEXTMENU + QMenu *createStandardContextMenu(const QPointF &pos, QWidget *parent); +#endif + + QTextCursor cursorForPosition(const QPointF &pos) const; + QRectF cursorRect(const QTextCursor &cursor) const; + QRectF cursorRect() const; + QRectF selectionRect(const QTextCursor &cursor) const; + QRectF selectionRect() const; + + QString anchorAt(const QPointF &pos) const; + QPointF anchorPosition(const QString &name) const; + + QString anchorAtCursor() const; + + bool overwriteMode() const; + void setOverwriteMode(bool overwrite); + + int cursorWidth() const; + void setCursorWidth(int width); + + bool acceptRichText() const; + void setAcceptRichText(bool accept); + +#ifndef QT_NO_TEXTEDIT + void setExtraSelections(const QList &selections); + QList extraSelections() const; +#endif + + void setTextWidth(qreal width); + qreal textWidth() const; + QSizeF size() const; + + void setOpenExternalLinks(bool open); + bool openExternalLinks() const; + + void setIgnoreUnusedNavigationEvents(bool ignore); + bool ignoreUnusedNavigationEvents() const; + + void moveCursor(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor); + + bool canPaste() const; + + void setCursorIsFocusIndicator(bool b); + bool cursorIsFocusIndicator() const; + + void setDragEnabled(bool enabled); + bool isDragEnabled() const; + + bool isWordSelectionEnabled() const; + void setWordSelectionEnabled(bool enabled); + +#ifndef QT_NO_PRINTER + void print(QPrinter *printer) const; +#endif + + virtual int hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const; + virtual QRectF blockBoundingRect(const QTextBlock &block) const; + QAbstractTextDocumentLayout::PaintContext getPaintContext(QWidget *widget) const; + +public Q_SLOTS: + void setPlainText(const QString &text); + void setHtml(const QString &text); + +#ifndef QT_NO_CLIPBOARD + void cut(); + void copy(); + void paste(QClipboard::Mode mode = QClipboard::Clipboard); +#endif + + void undo(); + void redo(); + + void clear(); + void selectAll(); + + void insertPlainText(const QString &text); +#ifndef QT_NO_TEXTHTMLPARSER + void insertHtml(const QString &text); +#endif + + void append(const QString &text); + void appendHtml(const QString &html); + void appendPlainText(const QString &text); + + void adjustSize(); + +Q_SIGNALS: + void textChanged(); + void undoAvailable(bool b); + void redoAvailable(bool b); + void currentCharFormatChanged(const QTextCharFormat &format); + void copyAvailable(bool b); + void selectionChanged(); + void cursorPositionChanged(); + + // control signals + void updateRequest(const QRectF &rect = QRectF()); + void documentSizeChanged(const QSizeF &); + void blockCountChanged(int newBlockCount); + void visibilityRequest(const QRectF &rect); + void microFocusChanged(); + void linkActivated(const QString &link); + void linkHovered(const QString &); + void modificationChanged(bool m); + +public: + // control properties + QPalette palette() const; + void setPalette(const QPalette &pal); + + virtual void processEvent(QEvent *e, const QMatrix &matrix, QWidget *contextWidget = 0); + void processEvent(QEvent *e, const QPointF &coordinateOffset = QPointF(), QWidget *contextWidget = 0); + + // control methods + void drawContents(QPainter *painter, const QRectF &rect = QRectF(), QWidget *widget = 0); + + void setFocus(bool focus, Qt::FocusReason = Qt::OtherFocusReason); + + virtual QVariant inputMethodQuery(Qt::InputMethodQuery property) const; + + virtual QMimeData *createMimeDataFromSelection() const; + virtual bool canInsertFromMimeData(const QMimeData *source) const; + virtual void insertFromMimeData(const QMimeData *source); + + bool setFocusToAnchor(const QTextCursor &newCursor); + bool setFocusToNextOrPreviousAnchor(bool next); + bool findNextPrevAnchor(const QTextCursor& from, bool next, QTextCursor& newAnchor); + +protected: + virtual void timerEvent(QTimerEvent *e); + + virtual bool event(QEvent *e); + +private: + Q_DISABLE_COPY(QTextControl) + Q_PRIVATE_SLOT(d_func(), void _q_updateCurrentCharFormatAndSelection()) + Q_PRIVATE_SLOT(d_func(), void _q_emitCursorPosChanged(const QTextCursor &)) + Q_PRIVATE_SLOT(d_func(), void _q_deleteSelected()) + Q_PRIVATE_SLOT(d_func(), void _q_copyLink()) + Q_PRIVATE_SLOT(d_func(), void _q_updateBlock(const QTextBlock &)) + Q_PRIVATE_SLOT(d_func(), void _q_documentLayoutChanged()) +}; + + +#ifndef QT_NO_CONTEXTMENU +class QUnicodeControlCharacterMenu : public QMenu +{ + Q_OBJECT +public: + QUnicodeControlCharacterMenu(QObject *editWidget, QWidget *parent); + +private Q_SLOTS: + void menuActionTriggered(); + +private: + QObject *editWidget; +}; +#endif // QT_NO_CONTEXTMENU + + +// also used by QLabel +class QTextEditMimeData : public QMimeData +{ +public: + inline QTextEditMimeData(const QTextDocumentFragment &aFragment) : fragment(aFragment) {} + + virtual QStringList formats() const; +protected: + virtual QVariant retrieveData(const QString &mimeType, QVariant::Type type) const; +private: + void setup() const; + + mutable QTextDocumentFragment fragment; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTEXTCONTROL_H diff --git a/src/gui/text/qtextcontrol_p_p.h b/src/gui/text/qtextcontrol_p_p.h new file mode 100644 index 0000000000..94670e225e --- /dev/null +++ b/src/gui/text/qtextcontrol_p_p.h @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTCONTROL_P_P_H +#define QTEXTCONTROL_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qtextdocumentfragment.h" +#include "QtGui/qscrollbar.h" +#include "QtGui/qtextcursor.h" +#include "QtGui/qtextformat.h" +#include "QtGui/qmenu.h" +#include "QtGui/qabstracttextdocumentlayout.h" +#include "QtCore/qbasictimer.h" +#include "QtCore/qpointer.h" +#include "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +class QMimeData; +class QAbstractScrollArea; +class QInputContext; + +class QTextControlPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QTextControl) +public: + QTextControlPrivate(); + + bool cursorMoveKeyEvent(QKeyEvent *e); + + void updateCurrentCharFormat(); + + void indent(); + void outdent(); + + void gotoNextTableCell(); + void gotoPreviousTableCell(); + + void createAutoBulletList(); + + void init(Qt::TextFormat format = Qt::RichText, const QString &text = QString(), + QTextDocument *document = 0); + void setContent(Qt::TextFormat format = Qt::RichText, const QString &text = QString(), + QTextDocument *document = 0); + void startDrag(); + + void paste(const QMimeData *source); + + void setCursorPosition(const QPointF &pos); + void setCursorPosition(int pos, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor); + + void repaintCursor(); + inline void repaintSelection() + { repaintOldAndNewSelection(QTextCursor()); } + void repaintOldAndNewSelection(const QTextCursor &oldSelection); + + void selectionChanged(bool forceEmitSelectionChanged = false); + + void _q_updateCurrentCharFormatAndSelection(); + +#ifndef QT_NO_CLIPBOARD + void setClipboardSelection(); +#endif + + void _q_emitCursorPosChanged(const QTextCursor &someCursor); + + void setBlinkingCursorEnabled(bool enable); + + void extendWordwiseSelection(int suggestedNewPosition, qreal mouseXPosition); + void extendBlockwiseSelection(int suggestedNewPosition); + + void _q_deleteSelected(); + + void _q_setCursorAfterUndoRedo(int undoPosition, int charsAdded, int charsRemoved); + + QRectF cursorRectPlusUnicodeDirectionMarkers(const QTextCursor &cursor) const; + QRectF rectForPosition(int position) const; + QRectF selectionRect(const QTextCursor &cursor) const; + inline QRectF selectionRect() const + { return selectionRect(this->cursor); } + + QString anchorForCursor(const QTextCursor &anchor) const; + + void keyPressEvent(QKeyEvent *e); + void mousePressEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, + Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, + const QPoint &globalPos); + void mouseMoveEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, + Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, + const QPoint &globalPos); + void mouseReleaseEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, + Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, + const QPoint &globalPos); + void mouseDoubleClickEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos, + Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, + const QPoint &globalPos); + bool sendMouseEventToInputContext(QEvent *e, QEvent::Type eventType, Qt::MouseButton button, + const QPointF &pos, + Qt::KeyboardModifiers modifiers, + Qt::MouseButtons buttons, + const QPoint &globalPos); + void contextMenuEvent(const QPoint &screenPos, const QPointF &docPos, QWidget *contextWidget); + void focusEvent(QFocusEvent *e); +#ifdef QT_KEYPAD_NAVIGATION + void editFocusEvent(QEvent *e); +#endif + bool dragEnterEvent(QEvent *e, const QMimeData *mimeData); + void dragLeaveEvent(); + bool dragMoveEvent(QEvent *e, const QMimeData *mimeData, const QPointF &pos); + bool dropEvent(const QMimeData *mimeData, const QPointF &pos, Qt::DropAction dropAction, QWidget *source); + + void inputMethodEvent(QInputMethodEvent *); + + void activateLinkUnderCursor(QString href = QString()); + +#ifndef QT_NO_TOOLTIP + void showToolTip(const QPoint &globalPos, const QPointF &pos, QWidget *contextWidget); +#endif + + void append(const QString &text, Qt::TextFormat format = Qt::AutoText); + + QInputContext *inputContext(); + + QTextDocument *doc; + bool cursorOn; + QTextCursor cursor; + bool cursorIsFocusIndicator; + QTextCharFormat lastCharFormat; + + QTextCursor dndFeedbackCursor; + + Qt::TextInteractionFlags interactionFlags; + + QBasicTimer cursorBlinkTimer; + QBasicTimer trippleClickTimer; + QPointF trippleClickPoint; + + bool dragEnabled; + + bool mousePressed; + + bool mightStartDrag; + QPoint dragStartPos; + QPointer contextWidget; + + bool lastSelectionState; + + bool ignoreAutomaticScrollbarAdjustement; + + QTextCursor selectedWordOnDoubleClick; + QTextCursor selectedBlockOnTrippleClick; + + bool overwriteMode; + bool acceptRichText; + + int preeditCursor; + bool hideCursor; // used to hide the cursor in the preedit area + + QVector extraSelections; + + QPalette palette; + bool hasFocus; +#ifdef QT_KEYPAD_NAVIGATION + bool hasEditFocus; +#endif + bool isEnabled; + + QString highlightedAnchor; // Anchor below cursor + QString anchorOnMousePress; + bool hadSelectionOnMousePress; + + bool ignoreUnusedNavigationEvents; + bool openExternalLinks; + + bool wordSelectionEnabled; + + QString linkToCopy; + void _q_copyLink(); + void _q_updateBlock(const QTextBlock &); + void _q_documentLayoutChanged(); +}; + +QT_END_NAMESPACE + +#endif // QTEXTCONTROL_P_H diff --git a/src/gui/text/qtextcursor.cpp b/src/gui/text/qtextcursor.cpp new file mode 100644 index 0000000000..6ddfdb00e1 --- /dev/null +++ b/src/gui/text/qtextcursor.cpp @@ -0,0 +1,2561 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextcursor.h" +#include "qtextcursor_p.h" +#include "qglobal.h" +#include "qtextdocumentfragment.h" +#include "qtextdocumentfragment_p.h" +#include "qtextlist.h" +#include "qtexttable.h" +#include "qtexttable_p.h" +#include "qtextengine_p.h" +#include "qabstracttextdocumentlayout.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +enum { + AdjustPrev = 0x1, + AdjustUp = 0x3, + AdjustNext = 0x4, + AdjustDown = 0x12 +}; + +QTextCursorPrivate::QTextCursorPrivate(QTextDocumentPrivate *p) + : priv(p), x(0), position(0), anchor(0), adjusted_anchor(0), + currentCharFormat(-1), visualNavigation(false), keepPositionOnInsert(false), + changed(false) +{ + priv->addCursor(this); +} + +QTextCursorPrivate::QTextCursorPrivate(const QTextCursorPrivate &rhs) + : QSharedData(rhs) +{ + position = rhs.position; + anchor = rhs.anchor; + adjusted_anchor = rhs.adjusted_anchor; + priv = rhs.priv; + x = rhs.x; + currentCharFormat = rhs.currentCharFormat; + visualNavigation = rhs.visualNavigation; + keepPositionOnInsert = rhs.keepPositionOnInsert; + changed = rhs.changed; + priv->addCursor(this); +} + +QTextCursorPrivate::~QTextCursorPrivate() +{ + if (priv) + priv->removeCursor(this); +} + +QTextCursorPrivate::AdjustResult QTextCursorPrivate::adjustPosition(int positionOfChange, int charsAddedOrRemoved, QTextUndoCommand::Operation op) +{ + QTextCursorPrivate::AdjustResult result = QTextCursorPrivate::CursorMoved; + // not(!) <= , so that inserting text adjusts the cursor correctly + if (position < positionOfChange + || (position == positionOfChange + && (op == QTextUndoCommand::KeepCursor + || keepPositionOnInsert) + ) + ) { + result = CursorUnchanged; + } else { + if (charsAddedOrRemoved < 0 && position < positionOfChange - charsAddedOrRemoved) + position = positionOfChange; + else + position += charsAddedOrRemoved; + + currentCharFormat = -1; + } + + if (anchor >= positionOfChange + && (anchor != positionOfChange || op != QTextUndoCommand::KeepCursor)) { + if (charsAddedOrRemoved < 0 && anchor < positionOfChange - charsAddedOrRemoved) + anchor = positionOfChange; + else + anchor += charsAddedOrRemoved; + } + + if (adjusted_anchor >= positionOfChange + && (adjusted_anchor != positionOfChange || op != QTextUndoCommand::KeepCursor)) { + if (charsAddedOrRemoved < 0 && adjusted_anchor < positionOfChange - charsAddedOrRemoved) + adjusted_anchor = positionOfChange; + else + adjusted_anchor += charsAddedOrRemoved; + } + + return result; +} + +void QTextCursorPrivate::setX() +{ + if (priv->isInEditBlock()) { + x = -1; // mark dirty + return; + } + + QTextBlock block = this->block(); + const QTextLayout *layout = blockLayout(block); + int pos = position - block.position(); + + QTextLine line = layout->lineForTextPosition(pos); + if (line.isValid()) + x = line.cursorToX(pos); + else + x = -1; // delayed init. Makes movePosition() call setX later on again. +} + +void QTextCursorPrivate::remove() +{ + if (anchor == position) + return; + currentCharFormat = -1; + int pos1 = position; + int pos2 = adjusted_anchor; + QTextUndoCommand::Operation op = QTextUndoCommand::KeepCursor; + if (pos1 > pos2) { + pos1 = adjusted_anchor; + pos2 = position; + op = QTextUndoCommand::MoveCursor; + } + + // deleting inside table? -> delete only content + QTextTable *table = complexSelectionTable(); + if (table) { + priv->beginEditBlock(); + int startRow, startCol, numRows, numCols; + selectedTableCells(&startRow, &numRows, &startCol, &numCols); + clearCells(table, startRow, startCol, numRows, numCols, op); + adjusted_anchor = anchor = position; + priv->endEditBlock(); + } else { + priv->remove(pos1, pos2-pos1, op); + adjusted_anchor = anchor = position; + priv->finishEdit(); + } + +} + +void QTextCursorPrivate::clearCells(QTextTable *table, int startRow, int startCol, int numRows, int numCols, QTextUndoCommand::Operation op) +{ + priv->beginEditBlock(); + + for (int row = startRow; row < startRow + numRows; ++row) + for (int col = startCol; col < startCol + numCols; ++col) { + QTextTableCell cell = table->cellAt(row, col); + const int startPos = cell.firstPosition(); + const int endPos = cell.lastPosition(); + Q_ASSERT(startPos <= endPos); + priv->remove(startPos, endPos - startPos, op); + } + + priv->endEditBlock(); +} + +bool QTextCursorPrivate::canDelete(int pos) const +{ + QTextDocumentPrivate::FragmentIterator fit = priv->find(pos); + QTextCharFormat fmt = priv->formatCollection()->charFormat((*fit)->format); + return (fmt.objectIndex() == -1 || fmt.objectType() == QTextFormat::ImageObject); +} + +void QTextCursorPrivate::insertBlock(const QTextBlockFormat &format, const QTextCharFormat &charFormat) +{ + QTextFormatCollection *formats = priv->formatCollection(); + int idx = formats->indexForFormat(format); + Q_ASSERT(formats->format(idx).isBlockFormat()); + + priv->insertBlock(position, idx, formats->indexForFormat(charFormat)); + currentCharFormat = -1; +} + +void QTextCursorPrivate::adjustCursor(QTextCursor::MoveOperation m) +{ + adjusted_anchor = anchor; + if (position == anchor) + return; + + QTextFrame *f_position = priv->frameAt(position); + QTextFrame *f_anchor = priv->frameAt(adjusted_anchor); + + if (f_position != f_anchor) { + // find common parent frame + QList positionChain; + QList anchorChain; + QTextFrame *f = f_position; + while (f) { + positionChain.prepend(f); + f = f->parentFrame(); + } + f = f_anchor; + while (f) { + anchorChain.prepend(f); + f = f->parentFrame(); + } + Q_ASSERT(positionChain.at(0) == anchorChain.at(0)); + int i = 1; + int l = qMin(positionChain.size(), anchorChain.size()); + for (; i < l; ++i) { + if (positionChain.at(i) != anchorChain.at(i)) + break; + } + + if (m <= QTextCursor::WordLeft) { + if (i < positionChain.size()) + position = positionChain.at(i)->firstPosition() - 1; + } else { + if (i < positionChain.size()) + position = positionChain.at(i)->lastPosition() + 1; + } + if (position < adjusted_anchor) { + if (i < anchorChain.size()) + adjusted_anchor = anchorChain.at(i)->lastPosition() + 1; + } else { + if (i < anchorChain.size()) + adjusted_anchor = anchorChain.at(i)->firstPosition() - 1; + } + + f_position = positionChain.at(i-1); + } + + // same frame, either need to adjust to cell boundaries or return + QTextTable *table = qobject_cast(f_position); + if (!table) + return; + + QTextTableCell c_position = table->cellAt(position); + QTextTableCell c_anchor = table->cellAt(adjusted_anchor); + if (c_position != c_anchor) { + bool before; + int col_position = c_position.column(); + int col_anchor = c_anchor.column(); + if (col_position == col_anchor) { + before = c_position.row() < c_anchor.row(); + } else { + before = col_position < col_anchor; + } + + // adjust to cell boundaries + if (m <= QTextCursor::WordLeft) { + position = c_position.firstPosition(); + if (!before) + --position; + } else { + position = c_position.lastPosition(); + if (before) + ++position; + } + if (position < adjusted_anchor) + adjusted_anchor = c_anchor.lastPosition(); + else + adjusted_anchor = c_anchor.firstPosition(); + } + currentCharFormat = -1; +} + +void QTextCursorPrivate::aboutToRemoveCell(int from, int to) +{ + Q_ASSERT(from <= to); + if (position == anchor) + return; + + QTextTable *t = qobject_cast(priv->frameAt(position)); + if (!t) + return; + QTextTableCell removedCellFrom = t->cellAt(from); + QTextTableCell removedCellEnd = t->cellAt(to); + if (! removedCellFrom.isValid() || !removedCellEnd.isValid()) + return; + + int curFrom = position; + int curTo = adjusted_anchor; + if (curTo < curFrom) + qSwap(curFrom, curTo); + + QTextTableCell cellStart = t->cellAt(curFrom); + QTextTableCell cellEnd = t->cellAt(curTo); + + if (cellStart.row() >= removedCellFrom.row() && cellEnd.row() <= removedCellEnd.row() + && cellStart.column() >= removedCellFrom.column() + && cellEnd.column() <= removedCellEnd.column()) { // selection is completely removed + // find a new position, as close as possible to where we were. + QTextTableCell cell; + if (removedCellFrom.row() == 0 && removedCellEnd.row() == t->rows()-1) // removed n columns + cell = t->cellAt(cellStart.row(), removedCellEnd.column()+1); + else if (removedCellFrom.column() == 0 && removedCellEnd.column() == t->columns()-1) // removed n rows + cell = t->cellAt(removedCellEnd.row() + 1, cellStart.column()); + + int newPosition; + if (cell.isValid()) + newPosition = cell.firstPosition(); + else + newPosition = t->lastPosition()+1; + + setPosition(newPosition); + anchor = newPosition; + adjusted_anchor = newPosition; + x = 0; + } + else if (cellStart.row() >= removedCellFrom.row() && cellStart.row() <= removedCellEnd.row() + && cellEnd.row() > removedCellEnd.row()) { + int newPosition = t->cellAt(removedCellEnd.row() + 1, cellStart.column()).firstPosition(); + if (position < anchor) + position = newPosition; + else + anchor = adjusted_anchor = newPosition; + } + else if (cellStart.column() >= removedCellFrom.column() && cellStart.column() <= removedCellEnd.column() + && cellEnd.column() > removedCellEnd.column()) { + int newPosition = t->cellAt(cellStart.row(), removedCellEnd.column()+1).firstPosition(); + if (position < anchor) + position = newPosition; + else + anchor = adjusted_anchor = newPosition; + } +} + +bool QTextCursorPrivate::movePosition(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode) +{ + currentCharFormat = -1; + bool adjustX = true; + QTextBlock blockIt = block(); + + if (!blockIt.isValid()) + return false; + + if (op >= QTextCursor::Left && op <= QTextCursor::WordRight + && blockIt.textDirection() == Qt::RightToLeft) { + if (op == QTextCursor::Left) + op = QTextCursor::NextCharacter; + else if (op == QTextCursor::Right) + op = QTextCursor::PreviousCharacter; + else if (op == QTextCursor::WordLeft) + op = QTextCursor::NextWord; + else if (op == QTextCursor::WordRight) + op = QTextCursor::PreviousWord; + } + + const QTextLayout *layout = blockLayout(blockIt); + int relativePos = position - blockIt.position(); + QTextLine line; + if (!priv->isInEditBlock()) + line = layout->lineForTextPosition(relativePos); + + Q_ASSERT(priv->frameAt(position) == priv->frameAt(adjusted_anchor)); + + int newPosition = position; + + if (x == -1 && !priv->isInEditBlock() && (op == QTextCursor::Up || op == QTextCursor::Down)) + setX(); + + switch(op) { + case QTextCursor::NoMove: + return true; + + case QTextCursor::Start: + newPosition = 0; + break; + case QTextCursor::StartOfLine: { + newPosition = blockIt.position(); + if (line.isValid()) + newPosition += line.textStart(); + + break; + } + case QTextCursor::StartOfBlock: { + newPosition = blockIt.position(); + break; + } + case QTextCursor::PreviousBlock: { + if (blockIt == priv->blocksBegin()) + return false; + blockIt = blockIt.previous(); + + newPosition = blockIt.position(); + break; + } + case QTextCursor::PreviousCharacter: + case QTextCursor::Left: + newPosition = priv->previousCursorPosition(position, QTextLayout::SkipCharacters); + break; + case QTextCursor::StartOfWord: { + if (relativePos == 0) + break; + + // skip if already at word start + QTextEngine *engine = layout->engine(); + engine->attributes(); + if ((relativePos == blockIt.length() - 1) + && (engine->atSpace(relativePos - 1) || engine->atWordSeparator(relativePos - 1))) + return false; + + if (relativePos < blockIt.length()-1) + ++position; + + // FALL THROUGH! + } + case QTextCursor::PreviousWord: + case QTextCursor::WordLeft: + newPosition = priv->previousCursorPosition(position, QTextLayout::SkipWords); + break; + case QTextCursor::Up: { + int i = line.lineNumber() - 1; + if (i == -1) { + if (blockIt == priv->blocksBegin()) + return false; + int blockPosition = blockIt.position(); + QTextTable *table = qobject_cast(priv->frameAt(blockPosition)); + if (table) { + QTextTableCell cell = table->cellAt(blockPosition); + if (cell.firstPosition() == blockPosition) { + int row = cell.row() - 1; + if (row >= 0) { + blockPosition = table->cellAt(row, cell.column()).lastPosition(); + } else { + // move to line above the table + blockPosition = table->firstPosition() - 1; + } + blockIt = priv->blocksFind(blockPosition); + } else { + blockIt = blockIt.previous(); + } + } else { + blockIt = blockIt.previous(); + } + layout = blockLayout(blockIt); + i = layout->lineCount()-1; + } + if (layout->lineCount()) { + QTextLine line = layout->lineAt(i); + newPosition = line.xToCursor(x) + blockIt.position(); + } else { + newPosition = blockIt.position(); + } + adjustX = false; + break; + } + + case QTextCursor::End: + newPosition = priv->length() - 1; + break; + case QTextCursor::EndOfLine: { + if (!line.isValid() || line.textLength() == 0) { + if (blockIt.length() >= 1) + // position right before the block separator + newPosition = blockIt.position() + blockIt.length() - 1; + break; + } + newPosition = blockIt.position() + line.textStart() + line.textLength(); + if (line.lineNumber() < layout->lineCount() - 1) { + const QString text = blockIt.text(); + // ###### this relies on spaces being the cause for linebreaks. + // this doesn't work with japanese + if (text.at(line.textStart() + line.textLength() - 1).isSpace()) + --newPosition; + } + break; + } + case QTextCursor::EndOfWord: { + QTextEngine *engine = layout->engine(); + engine->attributes(); + const int len = blockIt.length() - 1; + if (relativePos >= len) + return false; + if (engine->atWordSeparator(relativePos)) { + ++relativePos; + while (relativePos < len && engine->atWordSeparator(relativePos)) + ++relativePos; + } else { + while (relativePos < len && !engine->atSpace(relativePos) && !engine->atWordSeparator(relativePos)) + ++relativePos; + } + newPosition = blockIt.position() + relativePos; + break; + } + case QTextCursor::EndOfBlock: + if (blockIt.length() >= 1) + // position right before the block separator + newPosition = blockIt.position() + blockIt.length() - 1; + break; + case QTextCursor::NextBlock: { + blockIt = blockIt.next(); + if (!blockIt.isValid()) + return false; + + newPosition = blockIt.position(); + break; + } + case QTextCursor::NextCharacter: + case QTextCursor::Right: + newPosition = priv->nextCursorPosition(position, QTextLayout::SkipCharacters); + break; + case QTextCursor::NextWord: + case QTextCursor::WordRight: + newPosition = priv->nextCursorPosition(position, QTextLayout::SkipWords); + break; + + case QTextCursor::Down: { + int i = line.lineNumber() + 1; + + if (i >= layout->lineCount()) { + int blockPosition = blockIt.position() + blockIt.length() - 1; + QTextTable *table = qobject_cast(priv->frameAt(blockPosition)); + if (table) { + QTextTableCell cell = table->cellAt(blockPosition); + if (cell.lastPosition() == blockPosition) { + int row = cell.row() + cell.rowSpan(); + if (row < table->rows()) { + blockPosition = table->cellAt(row, cell.column()).firstPosition(); + } else { + // move to line below the table + blockPosition = table->lastPosition() + 1; + } + blockIt = priv->blocksFind(blockPosition); + } else { + blockIt = blockIt.next(); + } + } else { + blockIt = blockIt.next(); + } + + if (blockIt == priv->blocksEnd()) + return false; + layout = blockLayout(blockIt); + i = 0; + } + if (layout->lineCount()) { + QTextLine line = layout->lineAt(i); + newPosition = line.xToCursor(x) + blockIt.position(); + } else { + newPosition = blockIt.position(); + } + adjustX = false; + break; + } + case QTextCursor::NextCell: // fall through + case QTextCursor::PreviousCell: // fall through + case QTextCursor::NextRow: // fall through + case QTextCursor::PreviousRow: { + QTextTable *table = qobject_cast(priv->frameAt(position)); + if (!table) + return false; + + QTextTableCell cell = table->cellAt(position); + Q_ASSERT(cell.isValid()); + int column = cell.column(); + int row = cell.row(); + const int currentRow = row; + if (op == QTextCursor::NextCell || op == QTextCursor::NextRow) { + do { + column += cell.columnSpan(); + if (column >= table->columns()) { + column = 0; + ++row; + } + cell = table->cellAt(row, column); + // note we also continue while we have not reached a cell thats not merged with one above us + } while (cell.isValid() + && ((op == QTextCursor::NextRow && currentRow == cell.row()) + || cell.row() < row)); + } + else if (op == QTextCursor::PreviousCell || op == QTextCursor::PreviousRow) { + do { + --column; + if (column < 0) { + column = table->columns()-1; + --row; + } + cell = table->cellAt(row, column); + // note we also continue while we have not reached a cell thats not merged with one above us + } while (cell.isValid() + && ((op == QTextCursor::PreviousRow && currentRow == cell.row()) + || cell.row() < row)); + } + if (cell.isValid()) + newPosition = cell.firstPosition(); + break; + } + } + + if (mode == QTextCursor::KeepAnchor) { + QTextTable *table = qobject_cast(priv->frameAt(position)); + if (table && ((op >= QTextCursor::PreviousBlock && op <= QTextCursor::WordLeft) + || (op >= QTextCursor::NextBlock && op <= QTextCursor::WordRight))) { + int oldColumn = table->cellAt(position).column(); + + const QTextTableCell otherCell = table->cellAt(newPosition); + if (!otherCell.isValid()) + return false; + + int newColumn = otherCell.column(); + if ((oldColumn > newColumn && op >= QTextCursor::End) + || (oldColumn < newColumn && op <= QTextCursor::WordLeft)) + return false; + } + } + + const bool moved = setPosition(newPosition); + + if (mode == QTextCursor::MoveAnchor) { + anchor = position; + adjusted_anchor = position; + } else { + adjustCursor(op); + } + + if (adjustX) + setX(); + + return moved; +} + +QTextTable *QTextCursorPrivate::complexSelectionTable() const +{ + if (position == anchor) + return 0; + + QTextTable *t = qobject_cast(priv->frameAt(position)); + if (t) { + QTextTableCell cell_pos = t->cellAt(position); + QTextTableCell cell_anchor = t->cellAt(adjusted_anchor); + + Q_ASSERT(cell_anchor.isValid()); + + if (cell_pos == cell_anchor) + t = 0; + } + return t; +} + +void QTextCursorPrivate::selectedTableCells(int *firstRow, int *numRows, int *firstColumn, int *numColumns) const +{ + *firstRow = -1; + *firstColumn = -1; + *numRows = -1; + *numColumns = -1; + + if (position == anchor) + return; + + QTextTable *t = qobject_cast(priv->frameAt(position)); + if (!t) + return; + + QTextTableCell cell_pos = t->cellAt(position); + QTextTableCell cell_anchor = t->cellAt(adjusted_anchor); + + Q_ASSERT(cell_anchor.isValid()); + + if (cell_pos == cell_anchor) + return; + + *firstRow = qMin(cell_pos.row(), cell_anchor.row()); + *firstColumn = qMin(cell_pos.column(), cell_anchor.column()); + *numRows = qMax(cell_pos.row() + cell_pos.rowSpan(), cell_anchor.row() + cell_anchor.rowSpan()) - *firstRow; + *numColumns = qMax(cell_pos.column() + cell_pos.columnSpan(), cell_anchor.column() + cell_anchor.columnSpan()) - *firstColumn; +} + +static void setBlockCharFormatHelper(QTextDocumentPrivate *priv, int pos1, int pos2, + const QTextCharFormat &format, QTextDocumentPrivate::FormatChangeMode changeMode) +{ + QTextBlock it = priv->blocksFind(pos1); + QTextBlock end = priv->blocksFind(pos2); + if (end.isValid()) + end = end.next(); + + for (; it != end; it = it.next()) { + priv->setCharFormat(it.position() - 1, 1, format, changeMode); + } +} + +void QTextCursorPrivate::setBlockCharFormat(const QTextCharFormat &_format, + QTextDocumentPrivate::FormatChangeMode changeMode) +{ + priv->beginEditBlock(); + + QTextCharFormat format = _format; + format.clearProperty(QTextFormat::ObjectIndex); + + QTextTable *table = complexSelectionTable(); + if (table) { + int row_start, col_start, num_rows, num_cols; + selectedTableCells(&row_start, &num_rows, &col_start, &num_cols); + + Q_ASSERT(row_start != -1); + for (int r = row_start; r < row_start + num_rows; ++r) { + for (int c = col_start; c < col_start + num_cols; ++c) { + QTextTableCell cell = table->cellAt(r, c); + int rspan = cell.rowSpan(); + int cspan = cell.columnSpan(); + if (rspan != 1) { + int cr = cell.row(); + if (cr != r) + continue; + } + if (cspan != 1) { + int cc = cell.column(); + if (cc != c) + continue; + } + + int pos1 = cell.firstPosition(); + int pos2 = cell.lastPosition(); + setBlockCharFormatHelper(priv, pos1, pos2, format, changeMode); + } + } + } else { + int pos1 = position; + int pos2 = adjusted_anchor; + if (pos1 > pos2) { + pos1 = adjusted_anchor; + pos2 = position; + } + + setBlockCharFormatHelper(priv, pos1, pos2, format, changeMode); + } + priv->endEditBlock(); +} + + +void QTextCursorPrivate::setBlockFormat(const QTextBlockFormat &format, QTextDocumentPrivate::FormatChangeMode changeMode) +{ + QTextTable *table = complexSelectionTable(); + if (table) { + priv->beginEditBlock(); + int row_start, col_start, num_rows, num_cols; + selectedTableCells(&row_start, &num_rows, &col_start, &num_cols); + + Q_ASSERT(row_start != -1); + for (int r = row_start; r < row_start + num_rows; ++r) { + for (int c = col_start; c < col_start + num_cols; ++c) { + QTextTableCell cell = table->cellAt(r, c); + int rspan = cell.rowSpan(); + int cspan = cell.columnSpan(); + if (rspan != 1) { + int cr = cell.row(); + if (cr != r) + continue; + } + if (cspan != 1) { + int cc = cell.column(); + if (cc != c) + continue; + } + + int pos1 = cell.firstPosition(); + int pos2 = cell.lastPosition(); + priv->setBlockFormat(priv->blocksFind(pos1), priv->blocksFind(pos2), format, changeMode); + } + } + priv->endEditBlock(); + } else { + int pos1 = position; + int pos2 = adjusted_anchor; + if (pos1 > pos2) { + pos1 = adjusted_anchor; + pos2 = position; + } + + priv->setBlockFormat(priv->blocksFind(pos1), priv->blocksFind(pos2), format, changeMode); + } +} + +void QTextCursorPrivate::setCharFormat(const QTextCharFormat &_format, QTextDocumentPrivate::FormatChangeMode changeMode) +{ + Q_ASSERT(position != anchor); + + QTextCharFormat format = _format; + format.clearProperty(QTextFormat::ObjectIndex); + + QTextTable *table = complexSelectionTable(); + if (table) { + priv->beginEditBlock(); + int row_start, col_start, num_rows, num_cols; + selectedTableCells(&row_start, &num_rows, &col_start, &num_cols); + + Q_ASSERT(row_start != -1); + for (int r = row_start; r < row_start + num_rows; ++r) { + for (int c = col_start; c < col_start + num_cols; ++c) { + QTextTableCell cell = table->cellAt(r, c); + int rspan = cell.rowSpan(); + int cspan = cell.columnSpan(); + if (rspan != 1) { + int cr = cell.row(); + if (cr != r) + continue; + } + if (cspan != 1) { + int cc = cell.column(); + if (cc != c) + continue; + } + + int pos1 = cell.firstPosition(); + int pos2 = cell.lastPosition(); + priv->setCharFormat(pos1, pos2-pos1, format, changeMode); + } + } + priv->endEditBlock(); + } else { + int pos1 = position; + int pos2 = adjusted_anchor; + if (pos1 > pos2) { + pos1 = adjusted_anchor; + pos2 = position; + } + + priv->setCharFormat(pos1, pos2-pos1, format, changeMode); + } +} + + +QTextLayout *QTextCursorPrivate::blockLayout(QTextBlock &block) const{ + QTextLayout *tl = block.layout(); + if (!tl->lineCount() && priv->layout()) + priv->layout()->blockBoundingRect(block); + return tl; +} + +/*! + \class QTextCursor + \reentrant + + \brief The QTextCursor class offers an API to access and modify QTextDocuments. + + \ingroup richtext-processing + \ingroup shared + + Text cursors are objects that are used to access and modify the + contents and underlying structure of text documents via a + programming interface that mimics the behavior of a cursor in a + text editor. QTextCursor contains information about both the + cursor's position within a QTextDocument and any selection that it + has made. + + QTextCursor is modeled on the way a text cursor behaves in a text + editor, providing a programmatic means of performing standard + actions through the user interface. A document can be thought of + as a single string of characters. The cursor's current position() + then is always either \e between two consecutive characters in the + string, or else \e before the very first character or \e after the + very last character in the string. Documents can also contain + tables, lists, images, and other objects in addition to text but, + from the developer's point of view, the document can be treated as + one long string. Some portions of that string can be considered + to lie within particular blocks (e.g. paragraphs), or within a + table's cell, or a list's item, or other structural elements. When + we refer to "current character" we mean the character immediately + \e before the cursor position() in the document. Similarly, the + "current block" is the block that contains the cursor position(). + + A QTextCursor also has an anchor() position. The text that is + between the anchor() and the position() is the selection. If + anchor() == position() there is no selection. + + The cursor position can be changed programmatically using + setPosition() and movePosition(); the latter can also be used to + select text. For selections see selectionStart(), selectionEnd(), + hasSelection(), clearSelection(), and removeSelectedText(). + + If the position() is at the start of a block atBlockStart() + returns true; and if it is at the end of a block atBlockEnd() returns + true. The format of the current character is returned by + charFormat(), and the format of the current block is returned by + blockFormat(). + + Formatting can be applied to the current text document using the + setCharFormat(), mergeCharFormat(), setBlockFormat() and + mergeBlockFormat() functions. The 'set' functions will replace the + cursor's current character or block format, while the 'merge' + functions add the given format properties to the cursor's current + format. If the cursor has a selection the given format is applied + to the current selection. Note that when only parts of a block is + selected the block format is applied to the entire block. The text + at the current character position can be turned into a list using + createList(). + + Deletions can be achieved using deleteChar(), + deletePreviousChar(), and removeSelectedText(). + + Text strings can be inserted into the document with the insertText() + function, blocks (representing new paragraphs) can be inserted with + insertBlock(). + + Existing fragments of text can be inserted with insertFragment() but, + if you want to insert pieces of text in various formats, it is usually + still easier to use insertText() and supply a character format. + + Various types of higher-level structure can also be inserted into the + document with the cursor: + + \list + \i Lists are ordered sequences of block elements that are decorated with + bullet points or symbols. These are inserted in a specified format + with insertList(). + \i Tables are inserted with the insertTable() function, and can be + given an optional format. These contain an array of cells that can + be traversed using the cursor. + \i Inline images are inserted with insertImage(). The image to be + used can be specified in an image format, or by name. + \i Frames are inserted by calling insertFrame() with a specified format. + \endlist + + Actions can be grouped (i.e. treated as a single action for + undo/redo) using beginEditBlock() and endEditBlock(). + + Cursor movements are limited to valid cursor positions. In Latin + writing this is between any two consecutive characters in the + text, before the first character, or after the last character. In + some other writing systems cursor movements are limited to + "clusters" (e.g. a syllable in Devanagari, or a base letter plus + diacritics). Functions such as movePosition() and deleteChar() + limit cursor movement to these valid positions. + + \sa \link richtext.html Rich Text Processing\endlink + +*/ + +/*! + \enum QTextCursor::MoveOperation + + \value NoMove Keep the cursor where it is + + \value Start Move to the start of the document. + \value StartOfLine Move to the start of the current line. + \value StartOfBlock Move to the start of the current block. + \value StartOfWord Move to the start of the current word. + \value PreviousBlock Move to the start of the previous block. + \value PreviousCharacter Move to the previous character. + \value PreviousWord Move to the beginning of the previous word. + \value Up Move up one line. + \value Left Move left one character. + \value WordLeft Move left one word. + + \value End Move to the end of the document. + \value EndOfLine Move to the end of the current line. + \value EndOfWord Move to the end of the current word. + \value EndOfBlock Move to the end of the current block. + \value NextBlock Move to the beginning of the next block. + \value NextCharacter Move to the next character. + \value NextWord Move to the next word. + \value Down Move down one line. + \value Right Move right one character. + \value WordRight Move right one word. + + \value NextCell Move to the beginning of the next table cell inside the + current table. If the current cell is the last cell in the row, the + cursor will move to the first cell in the next row. + \value PreviousCell Move to the beginning of the previous table cell + inside the current table. If the current cell is the first cell in + the row, the cursor will move to the last cell in the previous row. + \value NextRow Move to the first new cell of the next row in the current + table. + \value PreviousRow Move to the last cell of the previous row in the + current table. + + \sa movePosition() +*/ + +/*! + \enum QTextCursor::MoveMode + + \value MoveAnchor Moves the anchor to the same position as the cursor itself. + \value KeepAnchor Keeps the anchor where it is. + + If the anchor() is kept where it is and the position() is moved, + the text in between will be selected. +*/ + +/*! + \enum QTextCursor::SelectionType + + This enum describes the types of selection that can be applied with the + select() function. + + \value Document Selects the entire document. + \value BlockUnderCursor Selects the block of text under the cursor. + \value LineUnderCursor Selects the line of text under the cursor. + \value WordUnderCursor Selects the word under the cursor. If the cursor + is not positioned within a string of selectable characters, no + text is selected. +*/ + +/*! + Constructs a null cursor. + */ +QTextCursor::QTextCursor() + : d(0) +{ +} + +/*! + Constructs a cursor pointing to the beginning of the \a document. + */ +QTextCursor::QTextCursor(QTextDocument *document) + : d(new QTextCursorPrivate(document->docHandle())) +{ +} + +/*! + Constructs a cursor pointing to the beginning of the \a frame. +*/ +QTextCursor::QTextCursor(QTextFrame *frame) + : d(new QTextCursorPrivate(frame->document()->docHandle())) +{ + d->adjusted_anchor = d->anchor = d->position = frame->firstPosition(); +} + + +/*! + Constructs a cursor pointing to the beginning of the \a block. +*/ +QTextCursor::QTextCursor(const QTextBlock &block) + : d(new QTextCursorPrivate(block.docHandle())) +{ + d->adjusted_anchor = d->anchor = d->position = block.position(); +} + + +/*! + \internal + */ +QTextCursor::QTextCursor(QTextDocumentPrivate *p, int pos) + : d(new QTextCursorPrivate(p)) +{ + d->adjusted_anchor = d->anchor = d->position = pos; + + d->setX(); +} + +/*! + \internal +*/ +QTextCursor::QTextCursor(QTextCursorPrivate *d) +{ + Q_ASSERT(d); + this->d = d; +} + +/*! + Constructs a new cursor that is a copy of \a cursor. + */ +QTextCursor::QTextCursor(const QTextCursor &cursor) +{ + d = cursor.d; +} + +/*! + Makes a copy of \a cursor and assigns it to this QTextCursor. Note + that QTextCursor is an \l{Implicitly Shared Classes}{implicitly + shared} class. + + */ +QTextCursor &QTextCursor::operator=(const QTextCursor &cursor) +{ + d = cursor.d; + return *this; +} + +/*! + Destroys the QTextCursor. + */ +QTextCursor::~QTextCursor() +{ +} + +/*! + Returns true if the cursor is null; otherwise returns false. A null + cursor is created by the default constructor. + */ +bool QTextCursor::isNull() const +{ + return !d || !d->priv; +} + +/*! + Moves the cursor to the absolute position in the document specified by + \a pos using a \c MoveMode specified by \a m. The cursor is positioned + between characters. + + \sa position() movePosition() anchor() +*/ +void QTextCursor::setPosition(int pos, MoveMode m) +{ + if (!d || !d->priv) + return; + + if (pos < 0 || pos >= d->priv->length()) { + qWarning("QTextCursor::setPosition: Position '%d' out of range", pos); + return; + } + + d->setPosition(pos); + if (m == MoveAnchor) { + d->anchor = pos; + d->adjusted_anchor = pos; + } else { // keep anchor + QTextCursor::MoveOperation op; + if (pos < d->anchor) + op = QTextCursor::Left; + else + op = QTextCursor::Right; + d->adjustCursor(op); + } + d->setX(); +} + +/*! + Returns the absolute position of the cursor within the document. + The cursor is positioned between characters. + + \sa setPosition() movePosition() anchor() positionInBlock() +*/ +int QTextCursor::position() const +{ + if (!d || !d->priv) + return -1; + return d->position; +} + +/*! + \since 4.7 + Returns the relative position of the cursor within the block. + The cursor is positioned between characters. + + This is equivalent to \c{ position() - block().position()}. + + \sa position() +*/ +int QTextCursor::positionInBlock() const +{ + if (!d || !d->priv) + return 0; + return d->position - d->block().position(); +} + +/*! + Returns the anchor position; this is the same as position() unless + there is a selection in which case position() marks one end of the + selection and anchor() marks the other end. Just like the cursor + position, the anchor position is between characters. + + \sa position() setPosition() movePosition() selectionStart() selectionEnd() +*/ +int QTextCursor::anchor() const +{ + if (!d || !d->priv) + return -1; + return d->anchor; +} + +/*! + \fn bool QTextCursor::movePosition(MoveOperation operation, MoveMode mode, int n) + + Moves the cursor by performing the given \a operation \a n times, using the specified + \a mode, and returns true if all operations were completed successfully; otherwise + returns false. + + For example, if this function is repeatedly used to seek to the end of the next + word, it will eventually fail when the end of the document is reached. + + By default, the move operation is performed once (\a n = 1). + + If \a mode is \c KeepAnchor, the cursor selects the text it moves + over. This is the same effect that the user achieves when they + hold down the Shift key and move the cursor with the cursor keys. + + \sa setVisualNavigation() +*/ +bool QTextCursor::movePosition(MoveOperation op, MoveMode mode, int n) +{ + if (!d || !d->priv) + return false; + switch (op) { + case Start: + case StartOfLine: + case End: + case EndOfLine: + n = 1; + break; + default: break; + } + + int previousPosition = d->position; + for (; n > 0; --n) { + if (!d->movePosition(op, mode)) + return false; + } + + if (d->visualNavigation && !d->block().isVisible()) { + QTextBlock b = d->block(); + if (previousPosition < d->position) { + while (!b.next().isVisible()) + b = b.next(); + d->setPosition(b.position() + b.length() - 1); + } else { + while (!b.previous().isVisible()) + b = b.previous(); + d->setPosition(b.position()); + } + if (mode == QTextCursor::MoveAnchor) + d->anchor = d->position; + while (d->movePosition(op, mode) + && !d->block().isVisible()) + ; + + } + return true; +} + +/*! + \since 4.4 + + Returns true if the cursor does visual navigation; otherwise + returns false. + + Visual navigation means skipping over hidden text pragraphs. The + default is false. + + \sa setVisualNavigation(), movePosition() + */ +bool QTextCursor::visualNavigation() const +{ + return d ? d->visualNavigation : false; +} + +/*! + \since 4.4 + + Sets visual navigation to \a b. + + Visual navigation means skipping over hidden text pragraphs. The + default is false. + + \sa visualNavigation(), movePosition() + */ +void QTextCursor::setVisualNavigation(bool b) +{ + if (d) + d->visualNavigation = b; +} + + +/*! + \since 4.7 + + Sets the visual x position for vertical cursor movements to \a x. + + The vertical movement x position is cleared automatically when the cursor moves horizontally, and kept + unchanged when the cursor moves vertically. The mechanism allows the cursor to move up and down on a + visually straight line with proportional fonts, and to gently "jump" over short lines. + + A value of -1 indicates no predefined x position. It will then be set automatically the next time the + cursor moves up or down. + + \sa verticalMovementX() + */ +void QTextCursor::setVerticalMovementX(int x) +{ + if (d) + d->x = x; +} + +/*! \since 4.7 + + Returns the visual x position for vertical cursor movements. + + A value of -1 indicates no predefined x position. It will then be set automatically the next time the + cursor moves up or down. + + \sa setVerticalMovementX() + */ +int QTextCursor::verticalMovementX() const +{ + return d ? d->x : -1; +} + +/*! + \since 4.7 + + Returns whether the cursor should keep its current position when text gets inserted at the position of the + cursor. + + The default is false; + + \sa setKeepPositionOnInsert() + */ +bool QTextCursor::keepPositionOnInsert() const +{ + return d ? d->keepPositionOnInsert : false; +} + +/*! + \since 4.7 + + Defines whether the cursor should keep its current position when text gets inserted at the current position of the + cursor. + + If \a b is true, the cursor keeps its current position when text gets inserted at the positing of the cursor. + If \a b is false, the cursor moves along with the inserted text. + + The default is false. + + Note that a cursor always moves when text is inserted before the current position of the cursor, and it + always keeps its position when text is inserted after the current position of the cursor. + + \sa keepPositionOnInsert() + */ +void QTextCursor::setKeepPositionOnInsert(bool b) +{ + if (d) + d->keepPositionOnInsert = b; +} + + + +/*! + Inserts \a text at the current position, using the current + character format. + + If there is a selection, the selection is deleted and replaced by + \a text, for example: + \snippet doc/src/snippets/code/src_gui_text_qtextcursor.cpp 0 + This clears any existing selection, selects the word at the cursor + (i.e. from position() forward), and replaces the selection with + the phrase "Hello World". + + Any ASCII linefeed characters (\\n) in the inserted text are transformed + into unicode block separators, corresponding to insertBlock() calls. + + \sa charFormat() hasSelection() +*/ +void QTextCursor::insertText(const QString &text) +{ + QTextCharFormat fmt = charFormat(); + fmt.clearProperty(QTextFormat::ObjectType); + insertText(text, fmt); +} + +/*! + \fn void QTextCursor::insertText(const QString &text, const QTextCharFormat &format) + \overload + + Inserts \a text at the current position with the given \a format. +*/ +void QTextCursor::insertText(const QString &text, const QTextCharFormat &_format) +{ + if (!d || !d->priv) + return; + + Q_ASSERT(_format.isValid()); + + QTextCharFormat format = _format; + format.clearProperty(QTextFormat::ObjectIndex); + + bool hasEditBlock = false; + + if (d->anchor != d->position) { + hasEditBlock = true; + d->priv->beginEditBlock(); + d->remove(); + } + + if (!text.isEmpty()) { + QTextFormatCollection *formats = d->priv->formatCollection(); + int formatIdx = formats->indexForFormat(format); + Q_ASSERT(formats->format(formatIdx).isCharFormat()); + + QTextBlockFormat blockFmt = blockFormat(); + + + int textStart = d->priv->text.length(); + int blockStart = 0; + d->priv->text += text; + int textEnd = d->priv->text.length(); + + for (int i = 0; i < text.length(); ++i) { + QChar ch = text.at(i); + + const int blockEnd = i; + + if (ch == QLatin1Char('\r') + && (i + 1) < text.length() + && text.at(i + 1) == QLatin1Char('\n')) { + ++i; + ch = text.at(i); + } + + if (ch == QLatin1Char('\n') + || ch == QChar::ParagraphSeparator + || ch == QTextBeginningOfFrame + || ch == QTextEndOfFrame + || ch == QLatin1Char('\r')) { + + if (!hasEditBlock) { + hasEditBlock = true; + d->priv->beginEditBlock(); + } + + if (blockEnd > blockStart) + d->priv->insert(d->position, textStart + blockStart, blockEnd - blockStart, formatIdx); + + d->insertBlock(blockFmt, format); + blockStart = i + 1; + } + } + if (textStart + blockStart < textEnd) + d->priv->insert(d->position, textStart + blockStart, textEnd - textStart - blockStart, formatIdx); + } + if (hasEditBlock) + d->priv->endEditBlock(); + d->setX(); +} + +/*! + If there is no selected text, deletes the character \e at the + current cursor position; otherwise deletes the selected text. + + \sa deletePreviousChar() hasSelection() clearSelection() +*/ +void QTextCursor::deleteChar() +{ + if (!d || !d->priv) + return; + + if (d->position != d->anchor) { + removeSelectedText(); + return; + } + + if (!d->canDelete(d->position)) + return; + d->adjusted_anchor = d->anchor = + d->priv->nextCursorPosition(d->anchor, QTextLayout::SkipCharacters); + d->remove(); + d->setX(); +} + +/*! + If there is no selected text, deletes the character \e before the + current cursor position; otherwise deletes the selected text. + + \sa deleteChar() hasSelection() clearSelection() +*/ +void QTextCursor::deletePreviousChar() +{ + if (!d || !d->priv) + return; + + if (d->position != d->anchor) { + removeSelectedText(); + return; + } + + if (d->anchor < 1 || !d->canDelete(d->anchor-1)) + return; + d->anchor--; + + QTextDocumentPrivate::FragmentIterator fragIt = d->priv->find(d->anchor); + const QTextFragmentData * const frag = fragIt.value(); + int fpos = fragIt.position(); + QChar uc = d->priv->buffer().at(d->anchor - fpos + frag->stringPosition); + if (d->anchor > fpos && uc.unicode() >= 0xdc00 && uc.unicode() < 0xe000) { + // second half of a surrogate, check if we have the first half as well, + // if yes delete both at once + uc = d->priv->buffer().at(d->anchor - 1 - fpos + frag->stringPosition); + if (uc.unicode() >= 0xd800 && uc.unicode() < 0xdc00) + --d->anchor; + } + + d->adjusted_anchor = d->anchor; + d->remove(); + d->setX(); +} + +/*! + Selects text in the document according to the given \a selection. +*/ +void QTextCursor::select(SelectionType selection) +{ + if (!d || !d->priv) + return; + + clearSelection(); + + const QTextBlock block = d->block(); + + switch (selection) { + case LineUnderCursor: + movePosition(StartOfLine); + movePosition(EndOfLine, KeepAnchor); + break; + case WordUnderCursor: + movePosition(StartOfWord); + movePosition(EndOfWord, KeepAnchor); + break; + case BlockUnderCursor: + if (block.length() == 1) // no content + break; + movePosition(StartOfBlock); + // also select the paragraph separator + if (movePosition(PreviousBlock)) { + movePosition(EndOfBlock); + movePosition(NextBlock, KeepAnchor); + } + movePosition(EndOfBlock, KeepAnchor); + break; + case Document: + movePosition(Start); + movePosition(End, KeepAnchor); + break; + } +} + +/*! + Returns true if the cursor contains a selection; otherwise returns false. +*/ +bool QTextCursor::hasSelection() const +{ + return !!d && d->position != d->anchor; +} + + +/*! + Returns true if the cursor contains a selection that is not simply a + range from selectionStart() to selectionEnd(); otherwise returns false. + + Complex selections are ones that span at least two cells in a table; + their extent is specified by selectedTableCells(). +*/ +bool QTextCursor::hasComplexSelection() const +{ + if (!d) + return false; + + return d->complexSelectionTable() != 0; +} + +/*! + If the selection spans over table cells, \a firstRow is populated + with the number of the first row in the selection, \a firstColumn + with the number of the first column in the selection, and \a + numRows and \a numColumns with the number of rows and columns in + the selection. If the selection does not span any table cells the + results are harmless but undefined. +*/ +void QTextCursor::selectedTableCells(int *firstRow, int *numRows, int *firstColumn, int *numColumns) const +{ + *firstRow = -1; + *firstColumn = -1; + *numRows = -1; + *numColumns = -1; + + if (!d || d->position == d->anchor) + return; + + d->selectedTableCells(firstRow, numRows, firstColumn, numColumns); +} + + +/*! + Clears the current selection by setting the anchor to the cursor position. + + Note that it does \bold{not} delete the text of the selection. + + \sa removeSelectedText() hasSelection() +*/ +void QTextCursor::clearSelection() +{ + if (!d) + return; + d->adjusted_anchor = d->anchor = d->position; + d->currentCharFormat = -1; +} + +/*! + If there is a selection, its content is deleted; otherwise does + nothing. + + \sa hasSelection() +*/ +void QTextCursor::removeSelectedText() +{ + if (!d || !d->priv || d->position == d->anchor) + return; + + d->priv->beginEditBlock(); + d->remove(); + d->priv->endEditBlock(); + d->setX(); +} + +/*! + Returns the start of the selection or position() if the + cursor doesn't have a selection. + + \sa selectionEnd() position() anchor() +*/ +int QTextCursor::selectionStart() const +{ + if (!d || !d->priv) + return -1; + return qMin(d->position, d->adjusted_anchor); +} + +/*! + Returns the end of the selection or position() if the cursor + doesn't have a selection. + + \sa selectionStart() position() anchor() +*/ +int QTextCursor::selectionEnd() const +{ + if (!d || !d->priv) + return -1; + return qMax(d->position, d->adjusted_anchor); +} + +static void getText(QString &text, QTextDocumentPrivate *priv, const QString &docText, int pos, int end) +{ + while (pos < end) { + QTextDocumentPrivate::FragmentIterator fragIt = priv->find(pos); + const QTextFragmentData * const frag = fragIt.value(); + + const int offsetInFragment = qMax(0, pos - fragIt.position()); + const int len = qMin(int(frag->size_array[0] - offsetInFragment), end - pos); + + text += QString(docText.constData() + frag->stringPosition + offsetInFragment, len); + pos += len; + } +} + +/*! + Returns the current selection's text (which may be empty). This + only returns the text, with no rich text formatting information. + If you want a document fragment (i.e. formatted rich text) use + selection() instead. + + \note If the selection obtained from an editor spans a line break, + the text will contain a Unicode U+2029 paragraph separator character + instead of a newline \c{\n} character. Use QString::replace() to + replace these characters with newlines. +*/ +QString QTextCursor::selectedText() const +{ + if (!d || !d->priv || d->position == d->anchor) + return QString(); + + const QString docText = d->priv->buffer(); + QString text; + + QTextTable *table = d->complexSelectionTable(); + if (table) { + int row_start, col_start, num_rows, num_cols; + selectedTableCells(&row_start, &num_rows, &col_start, &num_cols); + + Q_ASSERT(row_start != -1); + for (int r = row_start; r < row_start + num_rows; ++r) { + for (int c = col_start; c < col_start + num_cols; ++c) { + QTextTableCell cell = table->cellAt(r, c); + int rspan = cell.rowSpan(); + int cspan = cell.columnSpan(); + if (rspan != 1) { + int cr = cell.row(); + if (cr != r) + continue; + } + if (cspan != 1) { + int cc = cell.column(); + if (cc != c) + continue; + } + + getText(text, d->priv, docText, cell.firstPosition(), cell.lastPosition()); + } + } + } else { + getText(text, d->priv, docText, selectionStart(), selectionEnd()); + } + + return text; +} + +/*! + Returns the current selection (which may be empty) with all its + formatting information. If you just want the selected text (i.e. + plain text) use selectedText() instead. + + \note Unlike QTextDocumentFragment::toPlainText(), + selectedText() may include special unicode characters such as + QChar::ParagraphSeparator. + + \sa QTextDocumentFragment::toPlainText() +*/ +QTextDocumentFragment QTextCursor::selection() const +{ + return QTextDocumentFragment(*this); +} + +/*! + Returns the block that contains the cursor. +*/ +QTextBlock QTextCursor::block() const +{ + if (!d || !d->priv) + return QTextBlock(); + return d->block(); +} + +/*! + Returns the block format of the block the cursor is in. + + \sa setBlockFormat() charFormat() + */ +QTextBlockFormat QTextCursor::blockFormat() const +{ + if (!d || !d->priv) + return QTextBlockFormat(); + + return d->block().blockFormat(); +} + +/*! + Sets the block format of the current block (or all blocks that + are contained in the selection) to \a format. + + \sa blockFormat(), mergeBlockFormat() +*/ +void QTextCursor::setBlockFormat(const QTextBlockFormat &format) +{ + if (!d || !d->priv) + return; + + d->setBlockFormat(format, QTextDocumentPrivate::SetFormat); +} + +/*! + Modifies the block format of the current block (or all blocks that + are contained in the selection) with the block format specified by + \a modifier. + + \sa setBlockFormat(), blockFormat() +*/ +void QTextCursor::mergeBlockFormat(const QTextBlockFormat &modifier) +{ + if (!d || !d->priv) + return; + + d->setBlockFormat(modifier, QTextDocumentPrivate::MergeFormat); +} + +/*! + Returns the block character format of the block the cursor is in. + + The block char format is the format used when inserting text at the + beginning of an empty block. + + \sa setBlockCharFormat() + */ +QTextCharFormat QTextCursor::blockCharFormat() const +{ + if (!d || !d->priv) + return QTextCharFormat(); + + return d->block().charFormat(); +} + +/*! + Sets the block char format of the current block (or all blocks that + are contained in the selection) to \a format. + + \sa blockCharFormat() +*/ +void QTextCursor::setBlockCharFormat(const QTextCharFormat &format) +{ + if (!d || !d->priv) + return; + + d->setBlockCharFormat(format, QTextDocumentPrivate::SetFormatAndPreserveObjectIndices); +} + +/*! + Modifies the block char format of the current block (or all blocks that + are contained in the selection) with the block format specified by + \a modifier. + + \sa setBlockCharFormat() +*/ +void QTextCursor::mergeBlockCharFormat(const QTextCharFormat &modifier) +{ + if (!d || !d->priv) + return; + + d->setBlockCharFormat(modifier, QTextDocumentPrivate::MergeFormat); +} + +/*! + Returns the format of the character immediately before the cursor + position(). If the cursor is positioned at the beginning of a text + block that is not empty then the format of the character + immediately after the cursor is returned. + + \sa insertText(), blockFormat() + */ +QTextCharFormat QTextCursor::charFormat() const +{ + if (!d || !d->priv) + return QTextCharFormat(); + + int idx = d->currentCharFormat; + if (idx == -1) { + QTextBlock block = d->block(); + + int pos; + if (d->position == block.position() + && block.length() > 1) + pos = d->position; + else + pos = d->position - 1; + + if (pos == -1) { + idx = d->priv->blockCharFormatIndex(d->priv->blockMap().firstNode()); + } else { + Q_ASSERT(pos >= 0 && pos < d->priv->length()); + + QTextDocumentPrivate::FragmentIterator it = d->priv->find(pos); + Q_ASSERT(!it.atEnd()); + idx = it.value()->format; + } + } + + QTextCharFormat cfmt = d->priv->formatCollection()->charFormat(idx); + cfmt.clearProperty(QTextFormat::ObjectIndex); + + Q_ASSERT(cfmt.isValid()); + return cfmt; +} + +/*! + Sets the cursor's current character format to the given \a + format. If the cursor has a selection, the given \a format is + applied to the current selection. + + \sa hasSelection(), mergeCharFormat() +*/ +void QTextCursor::setCharFormat(const QTextCharFormat &format) +{ + if (!d || !d->priv) + return; + if (d->position == d->anchor) { + d->currentCharFormat = d->priv->formatCollection()->indexForFormat(format); + return; + } + d->setCharFormat(format, QTextDocumentPrivate::SetFormatAndPreserveObjectIndices); +} + +/*! + Merges the cursor's current character format with the properties + described by format \a modifier. If the cursor has a selection, + this function applies all the properties set in \a modifier to all + the character formats that are part of the selection. + + \sa hasSelection(), setCharFormat() +*/ +void QTextCursor::mergeCharFormat(const QTextCharFormat &modifier) +{ + if (!d || !d->priv) + return; + if (d->position == d->anchor) { + QTextCharFormat format = charFormat(); + format.merge(modifier); + d->currentCharFormat = d->priv->formatCollection()->indexForFormat(format); + return; + } + + d->setCharFormat(modifier, QTextDocumentPrivate::MergeFormat); +} + +/*! + Returns true if the cursor is at the start of a block; otherwise + returns false. + + \sa atBlockEnd(), atStart() +*/ +bool QTextCursor::atBlockStart() const +{ + if (!d || !d->priv) + return false; + + return d->position == d->block().position(); +} + +/*! + Returns true if the cursor is at the end of a block; otherwise + returns false. + + \sa atBlockStart(), atEnd() +*/ +bool QTextCursor::atBlockEnd() const +{ + if (!d || !d->priv) + return false; + + return d->position == d->block().position() + d->block().length() - 1; +} + +/*! + Returns true if the cursor is at the start of the document; + otherwise returns false. + + \sa atBlockStart(), atEnd() +*/ +bool QTextCursor::atStart() const +{ + if (!d || !d->priv) + return false; + + return d->position == 0; +} + +/*! + \since 4.6 + + Returns true if the cursor is at the end of the document; + otherwise returns false. + + \sa atStart(), atBlockEnd() +*/ +bool QTextCursor::atEnd() const +{ + if (!d || !d->priv) + return false; + + return d->position == d->priv->length() - 1; +} + +/*! + Inserts a new empty block at the cursor position() with the + current blockFormat() and charFormat(). + + \sa setBlockFormat() +*/ +void QTextCursor::insertBlock() +{ + insertBlock(blockFormat()); +} + +/*! + \overload + + Inserts a new empty block at the cursor position() with block + format \a format and the current charFormat() as block char format. + + \sa setBlockFormat() +*/ +void QTextCursor::insertBlock(const QTextBlockFormat &format) +{ + QTextCharFormat charFmt = charFormat(); + charFmt.clearProperty(QTextFormat::ObjectType); + insertBlock(format, charFmt); +} + +/*! + \fn void QTextCursor::insertBlock(const QTextBlockFormat &format, const QTextCharFormat &charFormat) + \overload + + Inserts a new empty block at the cursor position() with block + format \a format and \a charFormat as block char format. + + \sa setBlockFormat() +*/ +void QTextCursor::insertBlock(const QTextBlockFormat &format, const QTextCharFormat &_charFormat) +{ + if (!d || !d->priv) + return; + + QTextCharFormat charFormat = _charFormat; + charFormat.clearProperty(QTextFormat::ObjectIndex); + + d->priv->beginEditBlock(); + d->remove(); + d->insertBlock(format, charFormat); + d->priv->endEditBlock(); + d->setX(); +} + +/*! + Inserts a new block at the current position and makes it the first + list item of a newly created list with the given \a format. Returns + the created list. + + \sa currentList() createList() insertBlock() + */ +QTextList *QTextCursor::insertList(const QTextListFormat &format) +{ + insertBlock(); + return createList(format); +} + +/*! + \overload + + Inserts a new block at the current position and makes it the first + list item of a newly created list with the given \a style. Returns + the created list. + + \sa currentList(), createList(), insertBlock() + */ +QTextList *QTextCursor::insertList(QTextListFormat::Style style) +{ + insertBlock(); + return createList(style); +} + +/*! + Creates and returns a new list with the given \a format, and makes the + current paragraph the cursor is in the first list item. + + \sa insertList() currentList() + */ +QTextList *QTextCursor::createList(const QTextListFormat &format) +{ + if (!d || !d->priv) + return 0; + + QTextList *list = static_cast(d->priv->createObject(format)); + QTextBlockFormat modifier; + modifier.setObjectIndex(list->objectIndex()); + mergeBlockFormat(modifier); + return list; +} + +/*! + \overload + + Creates and returns a new list with the given \a style, making the + cursor's current paragraph the first list item. + + The style to be used is defined by the QTextListFormat::Style enum. + + \sa insertList() currentList() + */ +QTextList *QTextCursor::createList(QTextListFormat::Style style) +{ + QTextListFormat fmt; + fmt.setStyle(style); + return createList(fmt); +} + +/*! + Returns the current list if the cursor position() is inside a + block that is part of a list; otherwise returns 0. + + \sa insertList() createList() + */ +QTextList *QTextCursor::currentList() const +{ + if (!d || !d->priv) + return 0; + + QTextBlockFormat b = blockFormat(); + QTextObject *o = d->priv->objectForFormat(b); + return qobject_cast(o); +} + +/*! + \fn QTextTable *QTextCursor::insertTable(int rows, int columns) + + \overload + + Creates a new table with the given number of \a rows and \a columns, + inserts it at the current cursor position() in the document, and returns + the table object. The cursor is moved to the beginning of the first cell. + + There must be at least one row and one column in the table. + + \sa currentTable() + */ +QTextTable *QTextCursor::insertTable(int rows, int cols) +{ + return insertTable(rows, cols, QTextTableFormat()); +} + +/*! + \fn QTextTable *QTextCursor::insertTable(int rows, int columns, const QTextTableFormat &format) + + Creates a new table with the given number of \a rows and \a columns + in the specified \a format, inserts it at the current cursor position() + in the document, and returns the table object. The cursor is moved to + the beginning of the first cell. + + There must be at least one row and one column in the table. + + \sa currentTable() +*/ +QTextTable *QTextCursor::insertTable(int rows, int cols, const QTextTableFormat &format) +{ + if(!d || !d->priv || rows == 0 || cols == 0) + return 0; + + int pos = d->position; + QTextTable *t = QTextTablePrivate::createTable(d->priv, d->position, rows, cols, format); + d->setPosition(pos+1); + // ##### what should we do if we have a selection? + d->anchor = d->position; + d->adjusted_anchor = d->anchor; + return t; +} + +/*! + Returns a pointer to the current table if the cursor position() + is inside a block that is part of a table; otherwise returns 0. + + \sa insertTable() +*/ +QTextTable *QTextCursor::currentTable() const +{ + if(!d || !d->priv) + return 0; + + QTextFrame *frame = d->priv->frameAt(d->position); + while (frame) { + QTextTable *table = qobject_cast(frame); + if (table) + return table; + frame = frame->parentFrame(); + } + return 0; +} + +/*! + Inserts a frame with the given \a format at the current cursor position(), + moves the cursor position() inside the frame, and returns the frame. + + If the cursor holds a selection, the whole selection is moved inside the + frame. + + \sa hasSelection() +*/ +QTextFrame *QTextCursor::insertFrame(const QTextFrameFormat &format) +{ + if (!d || !d->priv) + return 0; + + return d->priv->insertFrame(selectionStart(), selectionEnd(), format); +} + +/*! + Returns a pointer to the current frame. Returns 0 if the cursor is invalid. + + \sa insertFrame() +*/ +QTextFrame *QTextCursor::currentFrame() const +{ + if(!d || !d->priv) + return 0; + + return d->priv->frameAt(d->position); +} + + +/*! + Inserts the text \a fragment at the current position(). +*/ +void QTextCursor::insertFragment(const QTextDocumentFragment &fragment) +{ + if (!d || !d->priv || fragment.isEmpty()) + return; + + d->priv->beginEditBlock(); + d->remove(); + fragment.d->insert(*this); + d->priv->endEditBlock(); + + if (fragment.d && fragment.d->doc) + d->priv->mergeCachedResources(fragment.d->doc->docHandle()); +} + +/*! + \since 4.2 + Inserts the text \a html at the current position(). The text is interpreted as + HTML. + + \note When using this function with a style sheet, the style sheet will + only apply to the current block in the document. In order to apply a style + sheet throughout a document, use QTextDocument::setDefaultStyleSheet() + instead. +*/ + +#ifndef QT_NO_TEXTHTMLPARSER + +void QTextCursor::insertHtml(const QString &html) +{ + if (!d || !d->priv) + return; + QTextDocumentFragment fragment = QTextDocumentFragment::fromHtml(html, d->priv->document()); + insertFragment(fragment); +} + +#endif // QT_NO_TEXTHTMLPARSER + +/*! + \overload + \since 4.2 + + Inserts the image defined by the given \a format at the cursor's current position + with the specified \a alignment. + + \sa position() +*/ +void QTextCursor::insertImage(const QTextImageFormat &format, QTextFrameFormat::Position alignment) +{ + if (!d || !d->priv) + return; + + QTextFrameFormat ffmt; + ffmt.setPosition(alignment); + QTextObject *obj = d->priv->createObject(ffmt); + + QTextImageFormat fmt = format; + fmt.setObjectIndex(obj->objectIndex()); + + d->priv->beginEditBlock(); + d->remove(); + const int idx = d->priv->formatCollection()->indexForFormat(fmt); + d->priv->insert(d->position, QString(QChar(QChar::ObjectReplacementCharacter)), idx); + d->priv->endEditBlock(); +} + +/*! + Inserts the image defined by \a format at the current position(). +*/ +void QTextCursor::insertImage(const QTextImageFormat &format) +{ + insertText(QString(QChar::ObjectReplacementCharacter), format); +} + +/*! + \overload + + Convenience method for inserting the image with the given \a name at the + current position(). + + \snippet doc/src/snippets/code/src_gui_text_qtextcursor.cpp 1 +*/ +void QTextCursor::insertImage(const QString &name) +{ + QTextImageFormat format; + format.setName(name); + insertImage(format); +} + +/*! + \since 4.5 + \overload + + Convenience function for inserting the given \a image with an optional + \a name at the current position(). +*/ +void QTextCursor::insertImage(const QImage &image, const QString &name) +{ + if (image.isNull()) { + qWarning("QTextCursor::insertImage: attempt to add an invalid image"); + return; + } + QString imageName = name; + if (name.isEmpty()) + imageName = QString::number(image.serialNumber()); + d->priv->document()->addResource(QTextDocument::ImageResource, QUrl(imageName), image); + QTextImageFormat format; + format.setName(imageName); + insertImage(format); +} + +/*! + \fn bool QTextCursor::operator!=(const QTextCursor &other) const + + Returns true if the \a other cursor is at a different position in + the document as this cursor; otherwise returns false. +*/ +bool QTextCursor::operator!=(const QTextCursor &rhs) const +{ + return !operator==(rhs); +} + +/*! + \fn bool QTextCursor::operator<(const QTextCursor &other) const + + Returns true if the \a other cursor is positioned later in the + document than this cursor; otherwise returns false. +*/ +bool QTextCursor::operator<(const QTextCursor &rhs) const +{ + if (!d) + return !!rhs.d; + + if (!rhs.d) + return false; + + Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator<", "cannot compare cursors attached to different documents"); + + return d->position < rhs.d->position; +} + +/*! + \fn bool QTextCursor::operator<=(const QTextCursor &other) const + + Returns true if the \a other cursor is positioned later or at the + same position in the document as this cursor; otherwise returns + false. +*/ +bool QTextCursor::operator<=(const QTextCursor &rhs) const +{ + if (!d) + return true; + + if (!rhs.d) + return false; + + Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator<=", "cannot compare cursors attached to different documents"); + + return d->position <= rhs.d->position; +} + +/*! + \fn bool QTextCursor::operator==(const QTextCursor &other) const + + Returns true if the \a other cursor is at the same position in the + document as this cursor; otherwise returns false. +*/ +bool QTextCursor::operator==(const QTextCursor &rhs) const +{ + if (!d) + return !rhs.d; + + if (!rhs.d) + return false; + + return d->position == rhs.d->position && d->priv == rhs.d->priv; +} + +/*! + \fn bool QTextCursor::operator>=(const QTextCursor &other) const + + Returns true if the \a other cursor is positioned earlier or at the + same position in the document as this cursor; otherwise returns + false. +*/ +bool QTextCursor::operator>=(const QTextCursor &rhs) const +{ + if (!d) + return false; + + if (!rhs.d) + return true; + + Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator>=", "cannot compare cursors attached to different documents"); + + return d->position >= rhs.d->position; +} + +/*! + \fn bool QTextCursor::operator>(const QTextCursor &other) const + + Returns true if the \a other cursor is positioned earlier in the + document than this cursor; otherwise returns false. +*/ +bool QTextCursor::operator>(const QTextCursor &rhs) const +{ + if (!d) + return false; + + if (!rhs.d) + return true; + + Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator>", "cannot compare cursors attached to different documents"); + + return d->position > rhs.d->position; +} + +/*! + Indicates the start of a block of editing operations on the + document that should appear as a single operation from an + undo/redo point of view. + + For example: + + \snippet doc/src/snippets/code/src_gui_text_qtextcursor.cpp 2 + + The call to undo() will cause both insertions to be undone, + causing both "World" and "Hello" to be removed. + + It is possible to nest calls to beginEditBlock and endEditBlock. The + top-most pair will determine the scope of the undo/redo operation. + + \sa endEditBlock() + */ +void QTextCursor::beginEditBlock() +{ + if (!d || !d->priv) + return; + + if (d->priv->editBlock == 0) // we are the initial edit block, store current cursor position for undo + d->priv->editBlockCursorPosition = d->position; + + d->priv->beginEditBlock(); +} + +/*! + Like beginEditBlock() indicates the start of a block of editing operations + that should appear as a single operation for undo/redo. However unlike + beginEditBlock() it does not start a new block but reverses the previous call to + endEditBlock() and therefore makes following operations part of the previous edit block created. + + For example: + + \snippet doc/src/snippets/code/src_gui_text_qtextcursor.cpp 3 + + The call to undo() will cause all three insertions to be undone. + + \sa beginEditBlock(), endEditBlock() + */ +void QTextCursor::joinPreviousEditBlock() +{ + if (!d || !d->priv) + return; + + d->priv->joinPreviousEditBlock(); +} + +/*! + Indicates the end of a block of editing operations on the document + that should appear as a single operation from an undo/redo point + of view. + + \sa beginEditBlock() + */ + +void QTextCursor::endEditBlock() +{ + if (!d || !d->priv) + return; + + d->priv->endEditBlock(); +} + +/*! + Returns true if this cursor and \a other are copies of each other, i.e. + one of them was created as a copy of the other and neither has moved since. + This is much stricter than equality. + + \sa operator=() operator==() +*/ +bool QTextCursor::isCopyOf(const QTextCursor &other) const +{ + return d == other.d; +} + +/*! + \since 4.2 + Returns the number of the block the cursor is in, or 0 if the cursor is invalid. + + Note that this function only makes sense in documents without complex objects such + as tables or frames. +*/ +int QTextCursor::blockNumber() const +{ + if (!d || !d->priv) + return 0; + + return d->block().blockNumber(); +} + + +/*! + \since 4.2 + Returns the position of the cursor within its containing line. + + Note that this is the column number relative to a wrapped line, + not relative to the block (i.e. the paragraph). + + You probably want to call positionInBlock() instead. + + \sa positionInBlock() +*/ +int QTextCursor::columnNumber() const +{ + if (!d || !d->priv) + return 0; + + QTextBlock block = d->block(); + if (!block.isValid()) + return 0; + + const QTextLayout *layout = d->blockLayout(block); + + const int relativePos = d->position - block.position(); + + if (layout->lineCount() == 0) + return relativePos; + + QTextLine line = layout->lineForTextPosition(relativePos); + if (!line.isValid()) + return 0; + return relativePos - line.textStart(); +} + +/*! + \since 4.5 + Returns the document this cursor is associated with. +*/ +QTextDocument *QTextCursor::document() const +{ + if (d->priv) + return d->priv->document(); + return 0; // document went away +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextcursor.h b/src/gui/text/qtextcursor.h new file mode 100644 index 0000000000..4eaeb41ee9 --- /dev/null +++ b/src/gui/text/qtextcursor.h @@ -0,0 +1,240 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTCURSOR_H +#define QTEXTCURSOR_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QTextDocument; +class QTextCursorPrivate; +class QTextDocumentFragment; +class QTextCharFormat; +class QTextBlockFormat; +class QTextListFormat; +class QTextTableFormat; +class QTextFrameFormat; +class QTextImageFormat; +class QTextDocumentPrivate; +class QTextList; +class QTextTable; +class QTextFrame; +class QTextBlock; + +class Q_GUI_EXPORT QTextCursor +{ +public: + QTextCursor(); + explicit QTextCursor(QTextDocument *document); + QTextCursor(QTextDocumentPrivate *p, int pos); + explicit QTextCursor(QTextFrame *frame); + explicit QTextCursor(const QTextBlock &block); + explicit QTextCursor(QTextCursorPrivate *d); + QTextCursor(const QTextCursor &cursor); + QTextCursor &operator=(const QTextCursor &other); + ~QTextCursor(); + + bool isNull() const; + + enum MoveMode { + MoveAnchor, + KeepAnchor + }; + + void setPosition(int pos, MoveMode mode = MoveAnchor); + int position() const; + int positionInBlock() const; + + int anchor() const; + + void insertText(const QString &text); + void insertText(const QString &text, const QTextCharFormat &format); + + enum MoveOperation { + NoMove, + + Start, + Up, + StartOfLine, + StartOfBlock, + StartOfWord, + PreviousBlock, + PreviousCharacter, + PreviousWord, + Left, + WordLeft, + + End, + Down, + EndOfLine, + EndOfWord, + EndOfBlock, + NextBlock, + NextCharacter, + NextWord, + Right, + WordRight, + + NextCell, + PreviousCell, + NextRow, + PreviousRow + }; + + bool movePosition(MoveOperation op, MoveMode = MoveAnchor, int n = 1); + + bool visualNavigation() const; + void setVisualNavigation(bool b); + + void setVerticalMovementX(int x); + int verticalMovementX() const; + + void setKeepPositionOnInsert(bool b); + bool keepPositionOnInsert() const; + + void deleteChar(); + void deletePreviousChar(); + + enum SelectionType { + WordUnderCursor, + LineUnderCursor, + BlockUnderCursor, + Document + }; + void select(SelectionType selection); + + bool hasSelection() const; + bool hasComplexSelection() const; + void removeSelectedText(); + void clearSelection(); + int selectionStart() const; + int selectionEnd() const; + + QString selectedText() const; + QTextDocumentFragment selection() const; + void selectedTableCells(int *firstRow, int *numRows, int *firstColumn, int *numColumns) const; + + QTextBlock block() const; + + QTextCharFormat charFormat() const; + void setCharFormat(const QTextCharFormat &format); + void mergeCharFormat(const QTextCharFormat &modifier); + + QTextBlockFormat blockFormat() const; + void setBlockFormat(const QTextBlockFormat &format); + void mergeBlockFormat(const QTextBlockFormat &modifier); + + QTextCharFormat blockCharFormat() const; + void setBlockCharFormat(const QTextCharFormat &format); + void mergeBlockCharFormat(const QTextCharFormat &modifier); + + bool atBlockStart() const; + bool atBlockEnd() const; + bool atStart() const; + bool atEnd() const; + + void insertBlock(); + void insertBlock(const QTextBlockFormat &format); + void insertBlock(const QTextBlockFormat &format, const QTextCharFormat &charFormat); + + QTextList *insertList(const QTextListFormat &format); + QTextList *insertList(QTextListFormat::Style style); + + QTextList *createList(const QTextListFormat &format); + QTextList *createList(QTextListFormat::Style style); + QTextList *currentList() const; + + QTextTable *insertTable(int rows, int cols, const QTextTableFormat &format); + QTextTable *insertTable(int rows, int cols); + QTextTable *currentTable() const; + + QTextFrame *insertFrame(const QTextFrameFormat &format); + QTextFrame *currentFrame() const; + + void insertFragment(const QTextDocumentFragment &fragment); + +#ifndef QT_NO_TEXTHTMLPARSER + void insertHtml(const QString &html); +#endif // QT_NO_TEXTHTMLPARSER + + void insertImage(const QTextImageFormat &format, QTextFrameFormat::Position alignment); + void insertImage(const QTextImageFormat &format); + void insertImage(const QString &name); + void insertImage(const QImage &image, const QString &name = QString()); + + void beginEditBlock(); + void joinPreviousEditBlock(); + void endEditBlock(); + + bool operator!=(const QTextCursor &rhs) const; + bool operator<(const QTextCursor &rhs) const; + bool operator<=(const QTextCursor &rhs) const; + bool operator==(const QTextCursor &rhs) const; + bool operator>=(const QTextCursor &rhs) const; + bool operator>(const QTextCursor &rhs) const; + + bool isCopyOf(const QTextCursor &other) const; + + int blockNumber() const; + int columnNumber() const; + + QTextDocument *document() const; + +private: + QSharedDataPointer d; + friend class QTextDocumentFragmentPrivate; + friend class QTextCopyHelper; + friend class QTextControlPrivate; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTEXTCURSOR_H diff --git a/src/gui/text/qtextcursor_p.h b/src/gui/text/qtextcursor_p.h new file mode 100644 index 0000000000..566495e1a3 --- /dev/null +++ b/src/gui/text/qtextcursor_p.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTCURSOR_P_H +#define QTEXTCURSOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qtextcursor.h" +#include "qtextdocument.h" +#include "qtextdocument_p.h" +#include +#include "qtextobject.h" + +QT_BEGIN_NAMESPACE + +class QTextCursorPrivate : public QSharedData +{ +public: + QTextCursorPrivate(QTextDocumentPrivate *p); + QTextCursorPrivate(const QTextCursorPrivate &rhs); + ~QTextCursorPrivate(); + + enum AdjustResult { CursorMoved, CursorUnchanged }; + AdjustResult adjustPosition(int positionOfChange, int charsAddedOrRemoved, QTextUndoCommand::Operation op); + + void adjustCursor(QTextCursor::MoveOperation m); + + void remove(); + void clearCells(QTextTable *table, int startRow, int startCol, int numRows, int numCols, QTextUndoCommand::Operation op); + inline bool setPosition(int newPosition) { + Q_ASSERT(newPosition >= 0 && newPosition < priv->length()); + bool moved = position != newPosition; + if (moved) { + position = newPosition; + currentCharFormat = -1; + } + return moved; + } + void setX(); + bool canDelete(int pos) const; + + void insertBlock(const QTextBlockFormat &format, const QTextCharFormat &charFormat); + bool movePosition(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor); + + inline QTextBlock block() const + { return QTextBlock(priv, priv->blockMap().findNode(position)); } + inline QTextBlockFormat blockFormat() const + { return block().blockFormat(); } + + QTextLayout *blockLayout(QTextBlock &block) const; + + QTextTable *complexSelectionTable() const; + void selectedTableCells(int *firstRow, int *numRows, int *firstColumn, int *numColumns) const; + + void setBlockCharFormat(const QTextCharFormat &format, QTextDocumentPrivate::FormatChangeMode changeMode); + void setBlockFormat(const QTextBlockFormat &format, QTextDocumentPrivate::FormatChangeMode changeMode); + void setCharFormat(const QTextCharFormat &format, QTextDocumentPrivate::FormatChangeMode changeMode); + + void aboutToRemoveCell(int from, int to); + + QTextDocumentPrivate *priv; + qreal x; + int position; + int anchor; + int adjusted_anchor; + int currentCharFormat; + uint visualNavigation : 1; + uint keepPositionOnInsert : 1; + uint changed : 1; +}; + +QT_END_NAMESPACE + +#endif // QTEXTCURSOR_P_H diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp new file mode 100644 index 0000000000..6dbd755d93 --- /dev/null +++ b/src/gui/text/qtextdocument.cpp @@ -0,0 +1,3044 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextdocument.h" +#include +#include "qtextdocumentlayout_p.h" +#include "qtextdocumentfragment.h" +#include "qtextdocumentfragment_p.h" +#include "qtexttable.h" +#include "qtextlist.h" +#include +#include +#include +#include +#include +#include "qtexthtmlparser_p.h" +#include "qpainter.h" +#include "qprinter.h" +#include "qtextedit.h" +#include +#include +#include +#include +#include "qtextcontrol_p.h" +#include "qfont_p.h" +#include "private/qtextedit_p.h" +#include "private/qdataurl_p.h" + +#include "qtextdocument_p.h" +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +Q_CORE_EXPORT unsigned int qt_int_sqrt(unsigned int n); + +/*! + Returns true if the string \a text is likely to be rich text; + otherwise returns false. + + This function uses a fast and therefore simple heuristic. It + mainly checks whether there is something that looks like a tag + before the first line break. Although the result may be correct + for common cases, there is no guarantee. + + This function is defined in the \c header file. +*/ +bool Qt::mightBeRichText(const QString& text) +{ + if (text.isEmpty()) + return false; + int start = 0; + + while (start < text.length() && text.at(start).isSpace()) + ++start; + + // skip a leading as for example with xhtml + if (text.mid(start, 5) == QLatin1String("')) { + start += 2; + break; + } + ++start; + } + + while (start < text.length() && text.at(start).isSpace()) + ++start; + } + + if (text.mid(start, 5).toLower() == QLatin1String(" + ++open; + } + if (open < text.length() && text.at(open) == QLatin1Char('<')) { + const int close = text.indexOf(QLatin1Char('>'), open); + if (close > -1) { + QString tag; + for (int i = open+1; i < close; ++i) { + if (text[i].isDigit() || text[i].isLetter()) + tag += text[i]; + else if (!tag.isEmpty() && text[i].isSpace()) + break; + else if (!tag.isEmpty() && text[i] == QLatin1Char('/') && i + 1 == close) + break; + else if (!text[i].isSpace() && (!tag.isEmpty() || text[i] != QLatin1Char('!'))) + return false; // that's not a tag + } +#ifndef QT_NO_TEXTHTMLPARSER + return QTextHtmlParser::lookupElement(tag.toLower()) != -1; +#else + return false; +#endif // QT_NO_TEXTHTMLPARSER + } + } + return false; +} + +/*! + Converts the plain text string \a plain to a HTML string with + HTML metacharacters \c{<}, \c{>}, \c{&}, and \c{"} replaced by HTML + entities. + + Example: + + \snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 0 + + This function is defined in the \c header file. + + \sa convertFromPlainText(), mightBeRichText() +*/ +QString Qt::escape(const QString& plain) +{ + QString rich; + rich.reserve(int(plain.length() * 1.1)); + for (int i = 0; i < plain.length(); ++i) { + if (plain.at(i) == QLatin1Char('<')) + rich += QLatin1String("<"); + else if (plain.at(i) == QLatin1Char('>')) + rich += QLatin1String(">"); + else if (plain.at(i) == QLatin1Char('&')) + rich += QLatin1String("&"); + else if (plain.at(i) == QLatin1Char('"')) + rich += QLatin1String("""); + else + rich += plain.at(i); + } + return rich; +} + +/*! + \fn QString Qt::convertFromPlainText(const QString &plain, WhiteSpaceMode mode) + + Converts the plain text string \a plain to an HTML-formatted + paragraph while preserving most of its look. + + \a mode defines how whitespace is handled. + + This function is defined in the \c header file. + + \sa escape(), mightBeRichText() +*/ +QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode) +{ + int col = 0; + QString rich; + rich += QLatin1String("

"); + for (int i = 0; i < plain.length(); ++i) { + if (plain[i] == QLatin1Char('\n')){ + int c = 1; + while (i+1 < plain.length() && plain[i+1] == QLatin1Char('\n')) { + i++; + c++; + } + if (c == 1) + rich += QLatin1String("
\n"); + else { + rich += QLatin1String("

\n"); + while (--c > 1) + rich += QLatin1String("
\n"); + rich += QLatin1String("

"); + } + col = 0; + } else { + if (mode == Qt::WhiteSpacePre && plain[i] == QLatin1Char('\t')){ + rich += QChar(0x00a0U); + ++col; + while (col % 8) { + rich += QChar(0x00a0U); + ++col; + } + } + else if (mode == Qt::WhiteSpacePre && plain[i].isSpace()) + rich += QChar(0x00a0U); + else if (plain[i] == QLatin1Char('<')) + rich += QLatin1String("<"); + else if (plain[i] == QLatin1Char('>')) + rich += QLatin1String(">"); + else if (plain[i] == QLatin1Char('&')) + rich += QLatin1String("&"); + else + rich += plain[i]; + ++col; + } + } + if (col != 0) + rich += QLatin1String("

"); + return rich; +} + +#ifndef QT_NO_TEXTCODEC +/*! + \internal + + This function is defined in the \c header file. +*/ +QTextCodec *Qt::codecForHtml(const QByteArray &ba) +{ + return QTextCodec::codecForHtml(ba); +} +#endif + +/*! + \class QTextDocument + \reentrant + + \brief The QTextDocument class holds formatted text that can be + viewed and edited using a QTextEdit. + + \ingroup richtext-processing + + + QTextDocument is a container for structured rich text documents, providing + support for styled text and various types of document elements, such as + lists, tables, frames, and images. + They can be created for use in a QTextEdit, or used independently. + + Each document element is described by an associated format object. Each + format object is treated as a unique object by QTextDocuments, and can be + passed to objectForFormat() to obtain the document element that it is + applied to. + + A QTextDocument can be edited programmatically using a QTextCursor, and + its contents can be examined by traversing the document structure. The + entire document structure is stored as a hierarchy of document elements + beneath the root frame, found with the rootFrame() function. Alternatively, + if you just want to iterate over the textual contents of the document you + can use begin(), end(), and findBlock() to retrieve text blocks that you + can examine and iterate over. + + The layout of a document is determined by the documentLayout(); + you can create your own QAbstractTextDocumentLayout subclass and + set it using setDocumentLayout() if you want to use your own + layout logic. The document's title and other meta-information can be + obtained by calling the metaInformation() function. For documents that + are exposed to users through the QTextEdit class, the document title + is also available via the QTextEdit::documentTitle() function. + + The toPlainText() and toHtml() convenience functions allow you to retrieve the + contents of the document as plain text and HTML. + The document's text can be searched using the find() functions. + + Undo/redo of operations performed on the document can be controlled using + the setUndoRedoEnabled() function. The undo/redo system can be controlled + by an editor widget through the undo() and redo() slots; the document also + provides contentsChanged(), undoAvailable(), and redoAvailable() signals + that inform connected editor widgets about the state of the undo/redo + system. The following are the undo/redo operations of a QTextDocument: + + \list + \o Insertion or removal of characters. A sequence of insertions or removals + within the same text block are regarded as a single undo/redo operation. + \o Insertion or removal of text blocks. Sequences of insertion or removals + in a single operation (e.g., by selecting and then deleting text) are + regarded as a single undo/redo operation. + \o Text character format changes. + \o Text block format changes. + \o Text block group format changes. + \endlist + + \sa QTextCursor, QTextEdit, \link richtext.html Rich Text Processing\endlink , {Text Object Example} +*/ + +/*! + \property QTextDocument::defaultFont + \brief the default font used to display the document's text +*/ + +/*! + \property QTextDocument::defaultTextOption + \brief the default text option will be set on all \l{QTextLayout}s in the document. + + When \l{QTextBlock}s are created, the defaultTextOption is set on their + QTextLayout. This allows setting global properties for the document such as the + default word wrap mode. + */ + +/*! + Constructs an empty QTextDocument with the given \a parent. +*/ +QTextDocument::QTextDocument(QObject *parent) + : QObject(*new QTextDocumentPrivate, parent) +{ + Q_D(QTextDocument); + d->init(); +} + +/*! + Constructs a QTextDocument containing the plain (unformatted) \a text + specified, and with the given \a parent. +*/ +QTextDocument::QTextDocument(const QString &text, QObject *parent) + : QObject(*new QTextDocumentPrivate, parent) +{ + Q_D(QTextDocument); + d->init(); + QTextCursor(this).insertText(text); +} + +/*! + \internal +*/ +QTextDocument::QTextDocument(QTextDocumentPrivate &dd, QObject *parent) + : QObject(dd, parent) +{ + Q_D(QTextDocument); + d->init(); +} + +/*! + Destroys the document. +*/ +QTextDocument::~QTextDocument() +{ +} + + +/*! + Creates a new QTextDocument that is a copy of this text document. \a + parent is the parent of the returned text document. +*/ +QTextDocument *QTextDocument::clone(QObject *parent) const +{ + Q_D(const QTextDocument); + QTextDocument *doc = new QTextDocument(parent); + QTextCursor(doc).insertFragment(QTextDocumentFragment(this)); + doc->rootFrame()->setFrameFormat(rootFrame()->frameFormat()); + QTextDocumentPrivate *priv = doc->d_func(); + priv->title = d->title; + priv->url = d->url; + priv->pageSize = d->pageSize; + priv->indentWidth = d->indentWidth; + priv->defaultTextOption = d->defaultTextOption; + priv->setDefaultFont(d->defaultFont()); + priv->resources = d->resources; + priv->cachedResources.clear(); +#ifndef QT_NO_CSSPARSER + priv->defaultStyleSheet = d->defaultStyleSheet; + priv->parsedDefaultStyleSheet = d->parsedDefaultStyleSheet; +#endif + return doc; +} + +/*! + Returns true if the document is empty; otherwise returns false. +*/ +bool QTextDocument::isEmpty() const +{ + Q_D(const QTextDocument); + /* because if we're empty we still have one single paragraph as + * one single fragment */ + return d->length() <= 1; +} + +/*! + Clears the document. +*/ +void QTextDocument::clear() +{ + Q_D(QTextDocument); + d->clear(); + d->resources.clear(); +} + +/*! + \since 4.2 + + Undoes the last editing operation on the document if undo is + available. The provided \a cursor is positioned at the end of the + location where the edition operation was undone. + + See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework} + documentation for details. + + \sa undoAvailable(), isUndoRedoEnabled() +*/ +void QTextDocument::undo(QTextCursor *cursor) +{ + Q_D(QTextDocument); + const int pos = d->undoRedo(true); + if (cursor && pos >= 0) { + *cursor = QTextCursor(this); + cursor->setPosition(pos); + } +} + +/*! + \since 4.2 + Redoes the last editing operation on the document if \link + QTextDocument::isRedoAvailable() redo is available\endlink. + + The provided \a cursor is positioned at the end of the location where + the edition operation was redone. +*/ +void QTextDocument::redo(QTextCursor *cursor) +{ + Q_D(QTextDocument); + const int pos = d->undoRedo(false); + if (cursor && pos >= 0) { + *cursor = QTextCursor(this); + cursor->setPosition(pos); + } +} + +/*! \enum QTextDocument::Stacks + + \value UndoStack The undo stack. + \value RedoStack The redo stack. + \value UndoAndRedoStacks Both the undo and redo stacks. +*/ + +/*! + \since 4.7 + Clears the stacks specified by \a stacksToClear. + + This method clears any commands on the undo stack, the redo stack, + or both (the default). If commands are cleared, the appropriate + signals are emitted, QTextDocument::undoAvailable() or + QTextDocument::redoAvailable(). + + \sa QTextDocument::undoAvailable() QTextDocument::redoAvailable() +*/ +void QTextDocument::clearUndoRedoStacks(Stacks stacksToClear) +{ + Q_D(QTextDocument); + d->clearUndoRedoStacks(stacksToClear, true); +} + +/*! + \overload + +*/ +void QTextDocument::undo() +{ + Q_D(QTextDocument); + d->undoRedo(true); +} + +/*! + \overload + Redoes the last editing operation on the document if \link + QTextDocument::isRedoAvailable() redo is available\endlink. +*/ +void QTextDocument::redo() +{ + Q_D(QTextDocument); + d->undoRedo(false); +} + +/*! + \internal + + Appends a custom undo \a item to the undo stack. +*/ +void QTextDocument::appendUndoItem(QAbstractUndoItem *item) +{ + Q_D(QTextDocument); + d->appendUndoItem(item); +} + +/*! + \property QTextDocument::undoRedoEnabled + \brief whether undo/redo are enabled for this document + + This defaults to true. If disabled, the undo stack is cleared and + no items will be added to it. +*/ +void QTextDocument::setUndoRedoEnabled(bool enable) +{ + Q_D(QTextDocument); + d->enableUndoRedo(enable); +} + +bool QTextDocument::isUndoRedoEnabled() const +{ + Q_D(const QTextDocument); + return d->isUndoRedoEnabled(); +} + +/*! + \property QTextDocument::maximumBlockCount + \since 4.2 + \brief Specifies the limit for blocks in the document. + + Specifies the maximum number of blocks the document may have. If there are + more blocks in the document that specified with this property blocks are removed + from the beginning of the document. + + A negative or zero value specifies that the document may contain an unlimited + amount of blocks. + + The default value is 0. + + Note that setting this property will apply the limit immediately to the document + contents. + + Setting this property also disables the undo redo history. + + This property is undefined in documents with tables or frames. +*/ +int QTextDocument::maximumBlockCount() const +{ + Q_D(const QTextDocument); + return d->maximumBlockCount; +} + +void QTextDocument::setMaximumBlockCount(int maximum) +{ + Q_D(QTextDocument); + d->maximumBlockCount = maximum; + d->ensureMaximumBlockCount(); + setUndoRedoEnabled(false); +} + +/*! + \since 4.3 + + The default text option is used on all QTextLayout objects in the document. + This allows setting global properties for the document such as the default + word wrap mode. +*/ +QTextOption QTextDocument::defaultTextOption() const +{ + Q_D(const QTextDocument); + return d->defaultTextOption; +} + +/*! + \since 4.3 + + Sets the default text option. +*/ +void QTextDocument::setDefaultTextOption(const QTextOption &option) +{ + Q_D(QTextDocument); + d->defaultTextOption = option; + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); +} + +/*! + \fn void QTextDocument::markContentsDirty(int position, int length) + + Marks the contents specified by the given \a position and \a length + as "dirty", informing the document that it needs to be laid out + again. +*/ +void QTextDocument::markContentsDirty(int from, int length) +{ + Q_D(QTextDocument); + d->documentChange(from, length); + if (!d->inContentsChange) { + if (d->lout) { + d->lout->documentChanged(d->docChangeFrom, d->docChangeOldLength, d->docChangeLength); + d->docChangeFrom = -1; + } + } +} + +/*! + \property QTextDocument::useDesignMetrics + \since 4.1 + \brief whether the document uses design metrics of fonts to improve the accuracy of text layout + + If this property is set to true, the layout will use design metrics. + Otherwise, the metrics of the paint device as set on + QAbstractTextDocumentLayout::setPaintDevice() will be used. + + Using design metrics makes a layout have a width that is no longer dependent on hinting + and pixel-rounding. This means that WYSIWYG text layout becomes possible because the width + scales much more linearly based on paintdevice metrics than it would otherwise. + + By default, this property is false. +*/ + +void QTextDocument::setUseDesignMetrics(bool b) +{ + Q_D(QTextDocument); + if (b == d->defaultTextOption.useDesignMetrics()) + return; + d->defaultTextOption.setUseDesignMetrics(b); + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); +} + +bool QTextDocument::useDesignMetrics() const +{ + Q_D(const QTextDocument); + return d->defaultTextOption.useDesignMetrics(); +} + +/*! + \since 4.2 + + Draws the content of the document with painter \a p, clipped to \a rect. + If \a rect is a null rectangle (default) then the document is painted unclipped. +*/ +void QTextDocument::drawContents(QPainter *p, const QRectF &rect) +{ + p->save(); + QAbstractTextDocumentLayout::PaintContext ctx; + if (rect.isValid()) { + p->setClipRect(rect); + ctx.clip = rect; + } + documentLayout()->draw(p, ctx); + p->restore(); +} + +/*! + \property QTextDocument::textWidth + \since 4.2 + + The text width specifies the preferred width for text in the document. If + the text (or content in general) is wider than the specified with it is broken + into multiple lines and grows vertically. If the text cannot be broken into multiple + lines to fit into the specified text width it will be larger and the size() and the + idealWidth() property will reflect that. + + If the text width is set to -1 then the text will not be broken into multiple lines + unless it is enforced through an explicit line break or a new paragraph. + + The default value is -1. + + Setting the text width will also set the page height to -1, causing the document to + grow or shrink vertically in a continuous way. If you want the document layout to break + the text into multiple pages then you have to set the pageSize property instead. + + \sa size(), idealWidth(), pageSize() +*/ +void QTextDocument::setTextWidth(qreal width) +{ + Q_D(QTextDocument); + QSizeF sz = d->pageSize; + sz.setWidth(width); + sz.setHeight(-1); + setPageSize(sz); +} + +qreal QTextDocument::textWidth() const +{ + Q_D(const QTextDocument); + return d->pageSize.width(); +} + +/*! + \since 4.2 + + Returns the ideal width of the text document. The ideal width is the actually used width + of the document without optional alignments taken into account. It is always <= size().width(). + + \sa adjustSize(), textWidth +*/ +qreal QTextDocument::idealWidth() const +{ + if (QTextDocumentLayout *lout = qobject_cast(documentLayout())) + return lout->idealWidth(); + return textWidth(); +} + +/*! + \property QTextDocument::documentMargin + \since 4.5 + + The margin around the document. The default is 4. +*/ +qreal QTextDocument::documentMargin() const +{ + Q_D(const QTextDocument); + return d->documentMargin; +} + +void QTextDocument::setDocumentMargin(qreal margin) +{ + Q_D(QTextDocument); + if (d->documentMargin != margin) { + d->documentMargin = margin; + + QTextFrame* root = rootFrame(); + QTextFrameFormat format = root->frameFormat(); + format.setMargin(margin); + root->setFrameFormat(format); + + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); + } +} + + +/*! + \property QTextDocument::indentWidth + \since 4.4 + + Returns the width used for text list and text block indenting. + + The indent properties of QTextListFormat and QTextBlockFormat specify + multiples of this value. The default indent width is 40. +*/ +qreal QTextDocument::indentWidth() const +{ + Q_D(const QTextDocument); + return d->indentWidth; +} + + +/*! + \since 4.4 + + Sets the \a width used for text list and text block indenting. + + The indent properties of QTextListFormat and QTextBlockFormat specify + multiples of this value. The default indent width is 40 . + + \sa indentWidth() +*/ +void QTextDocument::setIndentWidth(qreal width) +{ + Q_D(QTextDocument); + if (d->indentWidth != width) { + d->indentWidth = width; + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); + } +} + + + + +/*! + \since 4.2 + + Adjusts the document to a reasonable size. + + \sa idealWidth(), textWidth, size +*/ +void QTextDocument::adjustSize() +{ + // Pull this private function in from qglobal.cpp + QFont f = defaultFont(); + QFontMetrics fm(f); + int mw = fm.width(QLatin1Char('x')) * 80; + int w = mw; + setTextWidth(w); + QSizeF size = documentLayout()->documentSize(); + if (size.width() != 0) { + w = qt_int_sqrt((uint)(5 * size.height() * size.width() / 3)); + setTextWidth(qMin(w, mw)); + + size = documentLayout()->documentSize(); + if (w*3 < 5*size.height()) { + w = qt_int_sqrt((uint)(2 * size.height() * size.width())); + setTextWidth(qMin(w, mw)); + } + } + setTextWidth(idealWidth()); +} + +/*! + \property QTextDocument::size + \since 4.2 + + Returns the actual size of the document. + This is equivalent to documentLayout()->documentSize(); + + The size of the document can be changed either by setting + a text width or setting an entire page size. + + Note that the width is always >= pageSize().width(). + + By default, for a newly-created, empty document, this property contains + a configuration-dependent size. + + \sa setTextWidth(), setPageSize(), idealWidth() +*/ +QSizeF QTextDocument::size() const +{ + return documentLayout()->documentSize(); +} + +/*! + \property QTextDocument::blockCount + \since 4.2 + + Returns the number of text blocks in the document. + + The value of this property is undefined in documents with tables or frames. + + By default, if defined, this property contains a value of 1. + \sa lineCount(), characterCount() +*/ +int QTextDocument::blockCount() const +{ + Q_D(const QTextDocument); + return d->blockMap().numNodes(); +} + + +/*! + \since 4.5 + + Returns the number of lines of this document (if the layout supports + this). Otherwise, this is identical to the number of blocks. + + \sa blockCount(), characterCount() + */ +int QTextDocument::lineCount() const +{ + Q_D(const QTextDocument); + return d->blockMap().length(2); +} + +/*! + \since 4.5 + + Returns the number of characters of this document. + + \sa blockCount(), characterAt() + */ +int QTextDocument::characterCount() const +{ + Q_D(const QTextDocument); + return d->length(); +} + +/*! + \since 4.5 + + Returns the character at position \a pos, or a null character if the + position is out of range. + + \sa characterCount() + */ +QChar QTextDocument::characterAt(int pos) const +{ + Q_D(const QTextDocument); + if (pos < 0 || pos >= d->length()) + return QChar(); + QTextDocumentPrivate::FragmentIterator fragIt = d->find(pos); + const QTextFragmentData * const frag = fragIt.value(); + const int offsetInFragment = qMax(0, pos - fragIt.position()); + return d->text.at(frag->stringPosition + offsetInFragment); +} + + +/*! + \property QTextDocument::defaultStyleSheet + \since 4.2 + + The default style sheet is applied to all newly HTML formatted text that is + inserted into the document, for example using setHtml() or QTextCursor::insertHtml(). + + The style sheet needs to be compliant to CSS 2.1 syntax. + + \bold{Note:} Changing the default style sheet does not have any effect to the existing content + of the document. + + \sa {Supported HTML Subset} +*/ + +#ifndef QT_NO_CSSPARSER +void QTextDocument::setDefaultStyleSheet(const QString &sheet) +{ + Q_D(QTextDocument); + d->defaultStyleSheet = sheet; + QCss::Parser parser(sheet); + d->parsedDefaultStyleSheet = QCss::StyleSheet(); + d->parsedDefaultStyleSheet.origin = QCss::StyleSheetOrigin_UserAgent; + parser.parse(&d->parsedDefaultStyleSheet); +} + +QString QTextDocument::defaultStyleSheet() const +{ + Q_D(const QTextDocument); + return d->defaultStyleSheet; +} +#endif // QT_NO_CSSPARSER + +/*! + \fn void QTextDocument::contentsChanged() + + This signal is emitted whenever the document's content changes; for + example, when text is inserted or deleted, or when formatting is applied. + + \sa contentsChange() +*/ + +/*! + \fn void QTextDocument::contentsChange(int position, int charsRemoved, int charsAdded) + + This signal is emitted whenever the document's content changes; for + example, when text is inserted or deleted, or when formatting is applied. + + Information is provided about the \a position of the character in the + document where the change occurred, the number of characters removed + (\a charsRemoved), and the number of characters added (\a charsAdded). + + The signal is emitted before the document's layout manager is notified + about the change. This hook allows you to implement syntax highlighting + for the document. + + \sa QAbstractTextDocumentLayout::documentChanged(), contentsChanged() +*/ + + +/*! + \fn QTextDocument::undoAvailable(bool available); + + This signal is emitted whenever undo operations become available + (\a available is true) or unavailable (\a available is false). + + See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework} + documentation for details. + + \sa undo(), isUndoRedoEnabled() +*/ + +/*! + \fn QTextDocument::redoAvailable(bool available); + + This signal is emitted whenever redo operations become available + (\a available is true) or unavailable (\a available is false). +*/ + +/*! + \fn QTextDocument::cursorPositionChanged(const QTextCursor &cursor); + + This signal is emitted whenever the position of a cursor changed + due to an editing operation. The cursor that changed is passed in + \a cursor. If you need a signal when the cursor is moved with the + arrow keys you can use the \l{QTextEdit::}{cursorPositionChanged()} signal in + QTextEdit. +*/ + +/*! + \fn QTextDocument::blockCountChanged(int newBlockCount); + \since 4.3 + + This signal is emitted when the total number of text blocks in the + document changes. The value passed in \a newBlockCount is the new + total. +*/ + +/*! + \fn QTextDocument::documentLayoutChanged(); + \since 4.4 + + This signal is emitted when a new document layout is set. + + \sa setDocumentLayout() + +*/ + + +/*! + Returns true if undo is available; otherwise returns false. + + \sa isRedoAvailable(), availableUndoSteps() +*/ +bool QTextDocument::isUndoAvailable() const +{ + Q_D(const QTextDocument); + return d->isUndoAvailable(); +} + +/*! + Returns true if redo is available; otherwise returns false. + + \sa isUndoAvailable(), availableRedoSteps() +*/ +bool QTextDocument::isRedoAvailable() const +{ + Q_D(const QTextDocument); + return d->isRedoAvailable(); +} + +/*! \since 4.6 + + Returns the number of available undo steps. + + \sa isUndoAvailable() +*/ +int QTextDocument::availableUndoSteps() const +{ + Q_D(const QTextDocument); + return d->availableUndoSteps(); +} + +/*! \since 4.6 + + Returns the number of available redo steps. + + \sa isRedoAvailable() +*/ +int QTextDocument::availableRedoSteps() const +{ + Q_D(const QTextDocument); + return d->availableRedoSteps(); +} + +/*! \since 4.4 + + Returns the document's revision (if undo is enabled). + + The revision is guaranteed to increase when a document that is not + modified is edited. + + \sa QTextBlock::revision(), isModified() + */ +int QTextDocument::revision() const +{ + Q_D(const QTextDocument); + return d->revision; +} + + + +/*! + Sets the document to use the given \a layout. The previous layout + is deleted. + + \sa documentLayoutChanged() +*/ +void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *layout) +{ + Q_D(QTextDocument); + d->setLayout(layout); +} + +/*! + Returns the document layout for this document. +*/ +QAbstractTextDocumentLayout *QTextDocument::documentLayout() const +{ + Q_D(const QTextDocument); + if (!d->lout) { + QTextDocument *that = const_cast(this); + that->d_func()->setLayout(new QTextDocumentLayout(that)); + } + return d->lout; +} + + +/*! + Returns meta information about the document of the type specified by + \a info. + + \sa setMetaInformation() +*/ +QString QTextDocument::metaInformation(MetaInformation info) const +{ + Q_D(const QTextDocument); + switch (info) { + case DocumentTitle: + return d->title; + case DocumentUrl: + return d->url; + } + return QString(); +} + +/*! + Sets the document's meta information of the type specified by \a info + to the given \a string. + + \sa metaInformation() +*/ +void QTextDocument::setMetaInformation(MetaInformation info, const QString &string) +{ + Q_D(QTextDocument); + switch (info) { + case DocumentTitle: + d->title = string; + break; + case DocumentUrl: + d->url = string; + break; + } +} + +/*! + Returns the plain text contained in the document. If you want + formatting information use a QTextCursor instead. + + \sa toHtml() +*/ +QString QTextDocument::toPlainText() const +{ + Q_D(const QTextDocument); + QString txt = d->plainText(); + + QChar *uc = txt.data(); + QChar *e = uc + txt.size(); + + for (; uc != e; ++uc) { + switch (uc->unicode()) { + case 0xfdd0: // QTextBeginningOfFrame + case 0xfdd1: // QTextEndOfFrame + case QChar::ParagraphSeparator: + case QChar::LineSeparator: + *uc = QLatin1Char('\n'); + break; + case QChar::Nbsp: + *uc = QLatin1Char(' '); + break; + default: + ; + } + } + return txt; +} + +/*! + Replaces the entire contents of the document with the given plain + \a text. + + \sa setHtml() +*/ +void QTextDocument::setPlainText(const QString &text) +{ + Q_D(QTextDocument); + bool previousState = d->isUndoRedoEnabled(); + d->enableUndoRedo(false); + d->beginEditBlock(); + d->clear(); + QTextCursor(this).insertText(text); + d->endEditBlock(); + d->enableUndoRedo(previousState); +} + +/*! + Replaces the entire contents of the document with the given + HTML-formatted text in the \a html string. + + The HTML formatting is respected as much as possible; for example, + "bold text" will produce text where the first word has a font + weight that gives it a bold appearance: "\bold{bold} text". + + \note It is the responsibility of the caller to make sure that the + text is correctly decoded when a QString containing HTML is created + and passed to setHtml(). + + \sa setPlainText(), {Supported HTML Subset} +*/ + +#ifndef QT_NO_TEXTHTMLPARSER + +void QTextDocument::setHtml(const QString &html) +{ + Q_D(QTextDocument); + bool previousState = d->isUndoRedoEnabled(); + d->enableUndoRedo(false); + d->beginEditBlock(); + d->clear(); + QTextHtmlImporter(this, html, QTextHtmlImporter::ImportToDocument).import(); + d->endEditBlock(); + d->enableUndoRedo(previousState); +} + +#endif // QT_NO_TEXTHTMLPARSER + +/*! + \enum QTextDocument::FindFlag + + This enum describes the options available to QTextDocument's find function. The options + can be OR-ed together from the following list: + + \value FindBackward Search backwards instead of forwards. + \value FindCaseSensitively By default find works case insensitive. Specifying this option + changes the behaviour to a case sensitive find operation. + \value FindWholeWords Makes find match only complete words. +*/ + +/*! + \enum QTextDocument::MetaInformation + + This enum describes the different types of meta information that can be + added to a document. + + \value DocumentTitle The title of the document. + \value DocumentUrl The url of the document. The loadResource() function uses + this url as the base when loading relative resources. + + \sa metaInformation(), setMetaInformation() +*/ + +/*! + \fn QTextCursor QTextDocument::find(const QString &subString, int position, FindFlags options) const + + \overload + + Finds the next occurrence of the string, \a subString, in the document. + The search starts at the given \a position, and proceeds forwards + through the document unless specified otherwise in the search options. + The \a options control the type of search performed. + + Returns a cursor with the match selected if \a subString + was found; otherwise returns a null cursor. + + If the \a position is 0 (the default) the search begins from the beginning + of the document; otherwise it begins at the specified position. +*/ +QTextCursor QTextDocument::find(const QString &subString, int from, FindFlags options) const +{ + QRegExp expr(subString); + expr.setPatternSyntax(QRegExp::FixedString); + expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive); + + return find(expr, from, options); +} + +/*! + \fn QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &cursor, FindFlags options) const + + Finds the next occurrence of the string, \a subString, in the document. + The search starts at the position of the given \a cursor, and proceeds + forwards through the document unless specified otherwise in the search + options. The \a options control the type of search performed. + + Returns a cursor with the match selected if \a subString was found; otherwise + returns a null cursor. + + If the given \a cursor has a selection, the search begins after the + selection; otherwise it begins at the cursor's position. + + By default the search is case-sensitive, and can match text anywhere in the + document. +*/ +QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &from, FindFlags options) const +{ + int pos = 0; + if (!from.isNull()) { + if (options & QTextDocument::FindBackward) + pos = from.selectionStart(); + else + pos = from.selectionEnd(); + } + QRegExp expr(subString); + expr.setPatternSyntax(QRegExp::FixedString); + expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive); + + return find(expr, pos, options); +} + + +static bool findInBlock(const QTextBlock &block, const QRegExp &expression, int offset, + QTextDocument::FindFlags options, QTextCursor &cursor) +{ + const QRegExp expr(expression); + QString text = block.text(); + text.replace(QChar::Nbsp, QLatin1Char(' ')); + + int idx = -1; + while (offset >=0 && offset <= text.length()) { + idx = (options & QTextDocument::FindBackward) ? + expr.lastIndexIn(text, offset) : expr.indexIn(text, offset); + if (idx == -1) + return false; + + if (options & QTextDocument::FindWholeWords) { + const int start = idx; + const int end = start + expr.matchedLength(); + if ((start != 0 && text.at(start - 1).isLetterOrNumber()) + || (end != text.length() && text.at(end).isLetterOrNumber())) { + //if this is not a whole word, continue the search in the string + offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1; + idx = -1; + continue; + } + } + //we have a hit, return the cursor for that. + break; + } + if (idx == -1) + return false; + cursor = QTextCursor(block.docHandle(), block.position() + idx); + cursor.setPosition(cursor.position() + expr.matchedLength(), QTextCursor::KeepAnchor); + return true; +} + +/*! + \fn QTextCursor QTextDocument::find(const QRegExp & expr, int position, FindFlags options) const + + \overload + + Finds the next occurrence, matching the regular expression, \a expr, in the document. + The search starts at the given \a position, and proceeds forwards + through the document unless specified otherwise in the search options. + The \a options control the type of search performed. The FindCaseSensitively + option is ignored for this overload, use QRegExp::caseSensitivity instead. + + Returns a cursor with the match selected if a match was found; otherwise + returns a null cursor. + + If the \a position is 0 (the default) the search begins from the beginning + of the document; otherwise it begins at the specified position. +*/ +QTextCursor QTextDocument::find(const QRegExp & expr, int from, FindFlags options) const +{ + Q_D(const QTextDocument); + + if (expr.isEmpty()) + return QTextCursor(); + + int pos = from; + //the cursor is positioned between characters, so for a backward search + //do not include the character given in the position. + if (options & FindBackward) { + --pos ; + if(pos < 0) + return QTextCursor(); + } + + QTextCursor cursor; + QTextBlock block = d->blocksFind(pos); + + if (!(options & FindBackward)) { + int blockOffset = qMax(0, pos - block.position()); + while (block.isValid()) { + if (findInBlock(block, expr, blockOffset, options, cursor)) + return cursor; + blockOffset = 0; + block = block.next(); + } + } else { + int blockOffset = pos - block.position(); + while (block.isValid()) { + if (findInBlock(block, expr, blockOffset, options, cursor)) + return cursor; + block = block.previous(); + blockOffset = block.length() - 1; + } + } + + return QTextCursor(); +} + +/*! + \fn QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &cursor, FindFlags options) const + + Finds the next occurrence, matching the regular expression, \a expr, in the document. + The search starts at the position of the given \a cursor, and proceeds + forwards through the document unless specified otherwise in the search + options. The \a options control the type of search performed. The FindCaseSensitively + option is ignored for this overload, use QRegExp::caseSensitivity instead. + + Returns a cursor with the match selected if a match was found; otherwise + returns a null cursor. + + If the given \a cursor has a selection, the search begins after the + selection; otherwise it begins at the cursor's position. + + By default the search is case-sensitive, and can match text anywhere in the + document. +*/ +QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &from, FindFlags options) const +{ + int pos = 0; + if (!from.isNull()) { + if (options & QTextDocument::FindBackward) + pos = from.selectionStart(); + else + pos = from.selectionEnd(); + } + return find(expr, pos, options); +} + + +/*! + \fn QTextObject *QTextDocument::createObject(const QTextFormat &format) + + Creates and returns a new document object (a QTextObject), based + on the given \a format. + + QTextObjects will always get created through this method, so you + must reimplement it if you use custom text objects inside your document. +*/ +QTextObject *QTextDocument::createObject(const QTextFormat &f) +{ + QTextObject *obj = 0; + if (f.isListFormat()) + obj = new QTextList(this); + else if (f.isTableFormat()) + obj = new QTextTable(this); + else if (f.isFrameFormat()) + obj = new QTextFrame(this); + + return obj; +} + +/*! + \internal + + Returns the frame that contains the text cursor position \a pos. +*/ +QTextFrame *QTextDocument::frameAt(int pos) const +{ + Q_D(const QTextDocument); + return d->frameAt(pos); +} + +/*! + Returns the document's root frame. +*/ +QTextFrame *QTextDocument::rootFrame() const +{ + Q_D(const QTextDocument); + return d->rootFrame(); +} + +/*! + Returns the text object associated with the given \a objectIndex. +*/ +QTextObject *QTextDocument::object(int objectIndex) const +{ + Q_D(const QTextDocument); + return d->objectForIndex(objectIndex); +} + +/*! + Returns the text object associated with the format \a f. +*/ +QTextObject *QTextDocument::objectForFormat(const QTextFormat &f) const +{ + Q_D(const QTextDocument); + return d->objectForFormat(f); +} + + +/*! + Returns the text block that contains the \a{pos}-th character. +*/ +QTextBlock QTextDocument::findBlock(int pos) const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().findNode(pos)); +} + +/*! + \since 4.4 + Returns the text block with the specified \a blockNumber. + + \sa QTextBlock::blockNumber() +*/ +QTextBlock QTextDocument::findBlockByNumber(int blockNumber) const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().findNode(blockNumber, 1)); +} + +/*! + \since 4.5 + Returns the text block that contains the specified \a lineNumber. + + \sa QTextBlock::firstLineNumber() +*/ +QTextBlock QTextDocument::findBlockByLineNumber(int lineNumber) const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().findNode(lineNumber, 2)); +} + +/*! + Returns the document's first text block. + + \sa firstBlock() +*/ +QTextBlock QTextDocument::begin() const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().begin().n); +} + +/*! + This function returns a block to test for the end of the document + while iterating over it. + + \snippet doc/src/snippets/textdocumentendsnippet.cpp 0 + + The block returned is invalid and represents the block after the + last block in the document. You can use lastBlock() to retrieve the + last valid block of the document. + + \sa lastBlock() +*/ +QTextBlock QTextDocument::end() const +{ + return QTextBlock(docHandle(), 0); +} + +/*! + \since 4.4 + Returns the document's first text block. +*/ +QTextBlock QTextDocument::firstBlock() const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().begin().n); +} + +/*! + \since 4.4 + Returns the document's last (valid) text block. +*/ +QTextBlock QTextDocument::lastBlock() const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().last().n); +} + +/*! + \property QTextDocument::pageSize + \brief the page size that should be used for laying out the document + + By default, for a newly-created, empty document, this property contains + an undefined size. + + \sa modificationChanged() +*/ + +void QTextDocument::setPageSize(const QSizeF &size) +{ + Q_D(QTextDocument); + d->pageSize = size; + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); +} + +QSizeF QTextDocument::pageSize() const +{ + Q_D(const QTextDocument); + return d->pageSize; +} + +/*! + returns the number of pages in this document. +*/ +int QTextDocument::pageCount() const +{ + return documentLayout()->pageCount(); +} + +/*! + Sets the default \a font to use in the document layout. +*/ +void QTextDocument::setDefaultFont(const QFont &font) +{ + Q_D(QTextDocument); + d->setDefaultFont(font); + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); +} + +/*! + Returns the default font to be used in the document layout. +*/ +QFont QTextDocument::defaultFont() const +{ + Q_D(const QTextDocument); + return d->defaultFont(); +} + +/*! + \fn QTextDocument::modificationChanged(bool changed) + + This signal is emitted whenever the content of the document + changes in a way that affects the modification state. If \a + changed is true, the document has been modified; otherwise it is + false. + + For example, calling setModified(false) on a document and then + inserting text causes the signal to get emitted. If you undo that + operation, causing the document to return to its original + unmodified state, the signal will get emitted again. +*/ + +/*! + \property QTextDocument::modified + \brief whether the document has been modified by the user + + By default, this property is false. + + \sa modificationChanged() +*/ + +bool QTextDocument::isModified() const +{ + return docHandle()->isModified(); +} + +void QTextDocument::setModified(bool m) +{ + docHandle()->setModified(m); +} + +#ifndef QT_NO_PRINTER +static void printPage(int index, QPainter *painter, const QTextDocument *doc, const QRectF &body, const QPointF &pageNumberPos) +{ + painter->save(); + painter->translate(body.left(), body.top() - (index - 1) * body.height()); + QRectF view(0, (index - 1) * body.height(), body.width(), body.height()); + + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + QAbstractTextDocumentLayout::PaintContext ctx; + + painter->setClipRect(view); + ctx.clip = view; + + // don't use the system palette text as default text color, on HP/UX + // for example that's white, and white text on white paper doesn't + // look that nice + ctx.palette.setColor(QPalette::Text, Qt::black); + + layout->draw(painter, ctx); + + if (!pageNumberPos.isNull()) { + painter->setClipping(false); + painter->setFont(QFont(doc->defaultFont())); + const QString pageString = QString::number(index); + + painter->drawText(qRound(pageNumberPos.x() - painter->fontMetrics().width(pageString)), + qRound(pageNumberPos.y() + view.top()), + pageString); + } + + painter->restore(); +} + +/*! + Prints the document to the given \a printer. The QPrinter must be + set up before being used with this function. + + This is only a convenience method to print the whole document to the printer. + + If the document is already paginated through a specified height in the pageSize() + property it is printed as-is. + + If the document is not paginated, like for example a document used in a QTextEdit, + then a temporary copy of the document is created and the copy is broken into + multiple pages according to the size of the QPrinter's paperRect(). By default + a 2 cm margin is set around the document contents. In addition the current page + number is printed at the bottom of each page. + + Note that QPrinter::Selection is not supported as print range with this function since + the selection is a property of QTextCursor. If you have a QTextEdit associated with + your QTextDocument then you can use QTextEdit's print() function because QTextEdit has + access to the user's selection. + + \sa QTextEdit::print() +*/ + +void QTextDocument::print(QPrinter *printer) const +{ + Q_D(const QTextDocument); + + if (!printer || !printer->isValid()) + return; + + if (!d->title.isEmpty()) + printer->setDocName(d->title); + + bool documentPaginated = d->pageSize.isValid() && !d->pageSize.isNull() + && d->pageSize.height() != INT_MAX; + + if (!documentPaginated && !printer->fullPage() && !printer->d_func()->hasCustomPageMargins) + printer->setPageMargins(23.53, 23.53, 23.53, 23.53, QPrinter::Millimeter); + + QPainter p(printer); + + // Check that there is a valid device to print to. + if (!p.isActive()) + return; + + const QTextDocument *doc = this; + QScopedPointer clonedDoc; + (void)doc->documentLayout(); // make sure that there is a layout + + QRectF body = QRectF(QPointF(0, 0), d->pageSize); + QPointF pageNumberPos; + + if (documentPaginated) { + qreal sourceDpiX = qt_defaultDpi(); + qreal sourceDpiY = sourceDpiX; + + QPaintDevice *dev = doc->documentLayout()->paintDevice(); + if (dev) { + sourceDpiX = dev->logicalDpiX(); + sourceDpiY = dev->logicalDpiY(); + } + + const qreal dpiScaleX = qreal(printer->logicalDpiX()) / sourceDpiX; + const qreal dpiScaleY = qreal(printer->logicalDpiY()) / sourceDpiY; + + // scale to dpi + p.scale(dpiScaleX, dpiScaleY); + + QSizeF scaledPageSize = d->pageSize; + scaledPageSize.rwidth() *= dpiScaleX; + scaledPageSize.rheight() *= dpiScaleY; + + const QSizeF printerPageSize(printer->pageRect().size()); + + // scale to page + p.scale(printerPageSize.width() / scaledPageSize.width(), + printerPageSize.height() / scaledPageSize.height()); + } else { + doc = clone(const_cast(this)); + clonedDoc.reset(const_cast(doc)); + + for (QTextBlock srcBlock = firstBlock(), dstBlock = clonedDoc->firstBlock(); + srcBlock.isValid() && dstBlock.isValid(); + srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) { + dstBlock.layout()->setAdditionalFormats(srcBlock.layout()->additionalFormats()); + } + + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + layout->setPaintDevice(p.device()); + + // copy the custom object handlers + layout->d_func()->handlers = documentLayout()->d_func()->handlers; + + int dpiy = p.device()->logicalDpiY(); + int margin = 0; + if (printer->fullPage() && !printer->d_func()->hasCustomPageMargins) { + // for compatibility + margin = (int) ((2/2.54)*dpiy); // 2 cm margins + QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); + fmt.setMargin(margin); + doc->rootFrame()->setFrameFormat(fmt); + } + + QRectF pageRect(printer->pageRect()); + body = QRectF(0, 0, pageRect.width(), pageRect.height()); + pageNumberPos = QPointF(body.width() - margin, + body.height() - margin + + QFontMetrics(doc->defaultFont(), p.device()).ascent() + + 5 * dpiy / 72.0); + clonedDoc->setPageSize(body.size()); + } + + int docCopies; + int pageCopies; + if (printer->collateCopies() == true){ + docCopies = 1; + pageCopies = printer->supportsMultipleCopies() ? 1 : printer->copyCount(); + } else { + docCopies = printer->supportsMultipleCopies() ? 1 : printer->copyCount(); + pageCopies = 1; + } + + int fromPage = printer->fromPage(); + int toPage = printer->toPage(); + bool ascending = true; + + if (fromPage == 0 && toPage == 0) { + fromPage = 1; + toPage = doc->pageCount(); + } + // paranoia check + fromPage = qMax(1, fromPage); + toPage = qMin(doc->pageCount(), toPage); + + if (toPage < fromPage) { + // if the user entered a page range outside the actual number + // of printable pages, just return + return; + } + + if (printer->pageOrder() == QPrinter::LastPageFirst) { + int tmp = fromPage; + fromPage = toPage; + toPage = tmp; + ascending = false; + } + + for (int i = 0; i < docCopies; ++i) { + + int page = fromPage; + while (true) { + for (int j = 0; j < pageCopies; ++j) { + if (printer->printerState() == QPrinter::Aborted + || printer->printerState() == QPrinter::Error) + return; + printPage(page, &p, doc, body, pageNumberPos); + if (j < pageCopies - 1) + printer->newPage(); + } + + if (page == toPage) + break; + + if (ascending) + ++page; + else + --page; + + printer->newPage(); + } + + if ( i < docCopies - 1) + printer->newPage(); + } +} +#endif + +/*! + \enum QTextDocument::ResourceType + + This enum describes the types of resources that can be loaded by + QTextDocument's loadResource() function. + + \value HtmlResource The resource contains HTML. + \value ImageResource The resource contains image data. + Currently supported data types are QVariant::Pixmap and + QVariant::Image. If the corresponding variant is of type + QVariant::ByteArray then Qt attempts to load the image using + QImage::loadFromData. QVariant::Icon is currently not supported. + The icon needs to be converted to one of the supported types first, + for example using QIcon::pixmap. + \value StyleSheetResource The resource contains CSS. + \value UserResource The first available value for user defined + resource types. + + \sa loadResource() +*/ + +/*! + Returns data of the specified \a type from the resource with the + given \a name. + + This function is called by the rich text engine to request data that isn't + directly stored by QTextDocument, but still associated with it. For example, + images are referenced indirectly by the name attribute of a QTextImageFormat + object. + + Resources are cached internally in the document. If a resource can + not be found in the cache, loadResource is called to try to load + the resource. loadResource should then use addResource to add the + resource to the cache. + + \sa QTextDocument::ResourceType +*/ +QVariant QTextDocument::resource(int type, const QUrl &name) const +{ + Q_D(const QTextDocument); + QVariant r = d->resources.value(name); + if (!r.isValid()) { + r = d->cachedResources.value(name); + if (!r.isValid()) + r = const_cast(this)->loadResource(type, name); + } + return r; +} + +/*! + Adds the resource \a resource to the resource cache, using \a + type and \a name as identifiers. \a type should be a value from + QTextDocument::ResourceType. + + For example, you can add an image as a resource in order to reference it + from within the document: + + \snippet snippets/textdocument-resources/main.cpp Adding a resource + + The image can be inserted into the document using the QTextCursor API: + + \snippet snippets/textdocument-resources/main.cpp Inserting an image with a cursor + + Alternatively, you can insert images using the HTML \c img tag: + + \snippet snippets/textdocument-resources/main.cpp Inserting an image using HTML +*/ +void QTextDocument::addResource(int type, const QUrl &name, const QVariant &resource) +{ + Q_UNUSED(type); + Q_D(QTextDocument); + d->resources.insert(name, resource); +} + +/*! + Loads data of the specified \a type from the resource with the + given \a name. + + This function is called by the rich text engine to request data that isn't + directly stored by QTextDocument, but still associated with it. For example, + images are referenced indirectly by the name attribute of a QTextImageFormat + object. + + When called by Qt, \a type is one of the values of + QTextDocument::ResourceType. + + If the QTextDocument is a child object of a QTextEdit, QTextBrowser, + or a QTextDocument itself then the default implementation tries + to retrieve the data from the parent. +*/ +QVariant QTextDocument::loadResource(int type, const QUrl &name) +{ + Q_D(QTextDocument); + QVariant r; + + QTextDocument *doc = qobject_cast(parent()); + if (doc) { + r = doc->loadResource(type, name); + } +#ifndef QT_NO_TEXTEDIT + else if (QTextEdit *edit = qobject_cast(parent())) { + QUrl resolvedName = edit->d_func()->resolveUrl(name); + r = edit->loadResource(type, resolvedName); + } +#endif +#ifndef QT_NO_TEXTCONTROL + else if (QTextControl *control = qobject_cast(parent())) { + r = control->loadResource(type, name); + } +#endif + + // handle data: URLs + if (r.isNull() && name.scheme().compare(QLatin1String("data"), Qt::CaseInsensitive) == 0) + r = qDecodeDataUrl(name).second; + + // if resource was not loaded try to load it here + if (!doc && r.isNull() && name.isRelative()) { + QUrl currentURL = d->url; + QUrl resourceUrl = name; + + // For the second case QUrl can merge "#someanchor" with "foo.html" + // correctly to "foo.html#someanchor" + if (!(currentURL.isRelative() + || (currentURL.scheme() == QLatin1String("file") + && !QFileInfo(currentURL.toLocalFile()).isAbsolute())) + || (name.hasFragment() && name.path().isEmpty())) { + resourceUrl = currentURL.resolved(name); + } else { + // this is our last resort when current url and new url are both relative + // we try to resolve against the current working directory in the local + // file system. + QFileInfo fi(currentURL.toLocalFile()); + if (fi.exists()) { + resourceUrl = + QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(name); + } else if (currentURL.isEmpty()) { + resourceUrl.setScheme(QLatin1String("file")); + } + } + + QString s = resourceUrl.toLocalFile(); + QFile f(s); + if (!s.isEmpty() && f.open(QFile::ReadOnly)) { + r = f.readAll(); + f.close(); + } + } + + if (!r.isNull()) { + if (type == ImageResource && r.type() == QVariant::ByteArray) { + if (qApp->thread() != QThread::currentThread()) { + // must use images in non-GUI threads + QImage image; + image.loadFromData(r.toByteArray()); + if (!image.isNull()) + r = image; + } else { + QPixmap pm; + pm.loadFromData(r.toByteArray()); + if (!pm.isNull()) + r = pm; + } + } + d->cachedResources.insert(name, r); + } + return r; +} + +static QTextFormat formatDifference(const QTextFormat &from, const QTextFormat &to) +{ + QTextFormat diff = to; + + const QMap props = to.properties(); + for (QMap::ConstIterator it = props.begin(), end = props.end(); + it != end; ++it) + if (it.value() == from.property(it.key())) + diff.clearProperty(it.key()); + + return diff; +} + +QTextHtmlExporter::QTextHtmlExporter(const QTextDocument *_doc) + : doc(_doc), fragmentMarkers(false) +{ + const QFont defaultFont = doc->defaultFont(); + defaultCharFormat.setFont(defaultFont); + // don't export those for the default font since we cannot turn them off with CSS + defaultCharFormat.clearProperty(QTextFormat::FontUnderline); + defaultCharFormat.clearProperty(QTextFormat::FontOverline); + defaultCharFormat.clearProperty(QTextFormat::FontStrikeOut); + defaultCharFormat.clearProperty(QTextFormat::TextUnderlineStyle); +} + +/*! + Returns the document in HTML format. The conversion may not be + perfect, especially for complex documents, due to the limitations + of HTML. +*/ +QString QTextHtmlExporter::toHtml(const QByteArray &encoding, ExportMode mode) +{ + html = QLatin1String("\n" + ""); + html.reserve(doc->docHandle()->length()); + + fragmentMarkers = (mode == ExportFragment); + + if (!encoding.isEmpty()) + html += QString::fromLatin1("").arg(QString::fromAscii(encoding)); + + QString title = doc->metaInformation(QTextDocument::DocumentTitle); + if (!title.isEmpty()) + html += QString::fromLatin1("") + title + QString::fromLatin1(""); + html += QLatin1String(""); + html += QLatin1String("rootFrame()->frameFormat(); + emitBackgroundAttribute(fmt); + + } else { + defaultCharFormat = QTextCharFormat(); + } + html += QLatin1Char('>'); + + QTextFrameFormat rootFmt = doc->rootFrame()->frameFormat(); + rootFmt.clearProperty(QTextFormat::BackgroundBrush); + + QTextFrameFormat defaultFmt; + defaultFmt.setMargin(doc->documentMargin()); + + if (rootFmt == defaultFmt) + emitFrame(doc->rootFrame()->begin()); + else + emitTextFrame(doc->rootFrame()); + + html += QLatin1String(""); + return html; +} + +void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value) +{ + html += QLatin1Char(' '); + html += QLatin1String(attribute); + html += QLatin1String("=\""); + html += Qt::escape(value); + html += QLatin1Char('"'); +} + +bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format) +{ + bool attributesEmitted = false; + + { + const QString family = format.fontFamily(); + if (!family.isEmpty() && family != defaultCharFormat.fontFamily()) { + emitFontFamily(family); + attributesEmitted = true; + } + } + + if (format.hasProperty(QTextFormat::FontPointSize) + && format.fontPointSize() != defaultCharFormat.fontPointSize()) { + html += QLatin1String(" font-size:"); + html += QString::number(format.fontPointSize()); + html += QLatin1String("pt;"); + attributesEmitted = true; + } else if (format.hasProperty(QTextFormat::FontSizeAdjustment)) { + static const char * const sizeNames[] = { + "small", "medium", "large", "x-large", "xx-large" + }; + const char *name = 0; + const int idx = format.intProperty(QTextFormat::FontSizeAdjustment) + 1; + if (idx >= 0 && idx <= 4) { + name = sizeNames[idx]; + } + if (name) { + html += QLatin1String(" font-size:"); + html += QLatin1String(name); + html += QLatin1Char(';'); + attributesEmitted = true; + } + } + + if (format.hasProperty(QTextFormat::FontWeight) + && format.fontWeight() != defaultCharFormat.fontWeight()) { + html += QLatin1String(" font-weight:"); + html += QString::number(format.fontWeight() * 8); + html += QLatin1Char(';'); + attributesEmitted = true; + } + + if (format.hasProperty(QTextFormat::FontItalic) + && format.fontItalic() != defaultCharFormat.fontItalic()) { + html += QLatin1String(" font-style:"); + html += (format.fontItalic() ? QLatin1String("italic") : QLatin1String("normal")); + html += QLatin1Char(';'); + attributesEmitted = true; + } + + QLatin1String decorationTag(" text-decoration:"); + html += decorationTag; + bool hasDecoration = false; + bool atLeastOneDecorationSet = false; + + if ((format.hasProperty(QTextFormat::FontUnderline) || format.hasProperty(QTextFormat::TextUnderlineStyle)) + && format.fontUnderline() != defaultCharFormat.fontUnderline()) { + hasDecoration = true; + if (format.fontUnderline()) { + html += QLatin1String(" underline"); + atLeastOneDecorationSet = true; + } + } + + if (format.hasProperty(QTextFormat::FontOverline) + && format.fontOverline() != defaultCharFormat.fontOverline()) { + hasDecoration = true; + if (format.fontOverline()) { + html += QLatin1String(" overline"); + atLeastOneDecorationSet = true; + } + } + + if (format.hasProperty(QTextFormat::FontStrikeOut) + && format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) { + hasDecoration = true; + if (format.fontStrikeOut()) { + html += QLatin1String(" line-through"); + atLeastOneDecorationSet = true; + } + } + + if (hasDecoration) { + if (!atLeastOneDecorationSet) + html += QLatin1String("none"); + html += QLatin1Char(';'); + attributesEmitted = true; + } else { + html.chop(qstrlen(decorationTag.latin1())); + } + + if (format.foreground() != defaultCharFormat.foreground() + && format.foreground().style() != Qt::NoBrush) { + html += QLatin1String(" color:"); + html += format.foreground().color().name(); + html += QLatin1Char(';'); + attributesEmitted = true; + } + + if (format.background() != defaultCharFormat.background() + && format.background().style() == Qt::SolidPattern) { + html += QLatin1String(" background-color:"); + html += format.background().color().name(); + html += QLatin1Char(';'); + attributesEmitted = true; + } + + if (format.verticalAlignment() != defaultCharFormat.verticalAlignment() + && format.verticalAlignment() != QTextCharFormat::AlignNormal) + { + html += QLatin1String(" vertical-align:"); + + QTextCharFormat::VerticalAlignment valign = format.verticalAlignment(); + if (valign == QTextCharFormat::AlignSubScript) + html += QLatin1String("sub"); + else if (valign == QTextCharFormat::AlignSuperScript) + html += QLatin1String("super"); + else if (valign == QTextCharFormat::AlignMiddle) + html += QLatin1String("middle"); + else if (valign == QTextCharFormat::AlignTop) + html += QLatin1String("top"); + else if (valign == QTextCharFormat::AlignBottom) + html += QLatin1String("bottom"); + + html += QLatin1Char(';'); + attributesEmitted = true; + } + + if (format.fontCapitalization() != QFont::MixedCase) { + const QFont::Capitalization caps = format.fontCapitalization(); + if (caps == QFont::AllUppercase) + html += QLatin1String(" text-transform:uppercase;"); + else if (caps == QFont::AllLowercase) + html += QLatin1String(" text-transform:lowercase;"); + else if (caps == QFont::SmallCaps) + html += QLatin1String(" font-variant:small-caps;"); + attributesEmitted = true; + } + + if (format.fontWordSpacing() != 0.0) { + html += QLatin1String(" word-spacing:"); + html += QString::number(format.fontWordSpacing()); + html += QLatin1String("px;"); + attributesEmitted = true; + } + + return attributesEmitted; +} + +void QTextHtmlExporter::emitTextLength(const char *attribute, const QTextLength &length) +{ + if (length.type() == QTextLength::VariableLength) // default + return; + + html += QLatin1Char(' '); + html += QLatin1String(attribute); + html += QLatin1String("=\""); + html += QString::number(length.rawValue()); + + if (length.type() == QTextLength::PercentageLength) + html += QLatin1String("%\""); + else + html += QLatin1Char('\"'); +} + +void QTextHtmlExporter::emitAlignment(Qt::Alignment align) +{ + if (align & Qt::AlignLeft) + return; + else if (align & Qt::AlignRight) + html += QLatin1String(" align=\"right\""); + else if (align & Qt::AlignHCenter) + html += QLatin1String(" align=\"center\""); + else if (align & Qt::AlignJustify) + html += QLatin1String(" align=\"justify\""); +} + +void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos, StyleMode mode) +{ + if (pos == QTextFrameFormat::InFlow) + return; + + if (mode == EmitStyleTag) + html += QLatin1String(" style=\"float:"); + else + html += QLatin1String(" float:"); + + if (pos == QTextFrameFormat::FloatLeft) + html += QLatin1String(" left;"); + else if (pos == QTextFrameFormat::FloatRight) + html += QLatin1String(" right;"); + else + Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type"); + + if (mode == EmitStyleTag) + html += QLatin1Char('\"'); +} + +void QTextHtmlExporter::emitBorderStyle(QTextFrameFormat::BorderStyle style) +{ + Q_ASSERT(style <= QTextFrameFormat::BorderStyle_Outset); + + html += QLatin1String(" border-style:"); + + switch (style) { + case QTextFrameFormat::BorderStyle_None: + html += QLatin1String("none"); + break; + case QTextFrameFormat::BorderStyle_Dotted: + html += QLatin1String("dotted"); + break; + case QTextFrameFormat::BorderStyle_Dashed: + html += QLatin1String("dashed"); + break; + case QTextFrameFormat::BorderStyle_Solid: + html += QLatin1String("solid"); + break; + case QTextFrameFormat::BorderStyle_Double: + html += QLatin1String("double"); + break; + case QTextFrameFormat::BorderStyle_DotDash: + html += QLatin1String("dot-dash"); + break; + case QTextFrameFormat::BorderStyle_DotDotDash: + html += QLatin1String("dot-dot-dash"); + break; + case QTextFrameFormat::BorderStyle_Groove: + html += QLatin1String("groove"); + break; + case QTextFrameFormat::BorderStyle_Ridge: + html += QLatin1String("ridge"); + break; + case QTextFrameFormat::BorderStyle_Inset: + html += QLatin1String("inset"); + break; + case QTextFrameFormat::BorderStyle_Outset: + html += QLatin1String("outset"); + break; + default: + Q_ASSERT(false); + break; + }; + + html += QLatin1Char(';'); +} + +void QTextHtmlExporter::emitPageBreakPolicy(QTextFormat::PageBreakFlags policy) +{ + if (policy & QTextFormat::PageBreak_AlwaysBefore) + html += QLatin1String(" page-break-before:always;"); + + if (policy & QTextFormat::PageBreak_AlwaysAfter) + html += QLatin1String(" page-break-after:always;"); +} + +void QTextHtmlExporter::emitFontFamily(const QString &family) +{ + html += QLatin1String(" font-family:"); + + QLatin1String quote("\'"); + if (family.contains(QLatin1Char('\''))) + quote = QLatin1String("""); + + html += quote; + html += Qt::escape(family); + html += quote; + html += QLatin1Char(';'); +} + +void QTextHtmlExporter::emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right) +{ + html += QLatin1String(" margin-top:"); + html += top; + html += QLatin1String("px;"); + + html += QLatin1String(" margin-bottom:"); + html += bottom; + html += QLatin1String("px;"); + + html += QLatin1String(" margin-left:"); + html += left; + html += QLatin1String("px;"); + + html += QLatin1String(" margin-right:"); + html += right; + html += QLatin1String("px;"); +} + +void QTextHtmlExporter::emitFragment(const QTextFragment &fragment) +{ + const QTextCharFormat format = fragment.charFormat(); + + bool closeAnchor = false; + + if (format.isAnchor()) { + const QString name = format.anchorName(); + if (!name.isEmpty()) { + html += QLatin1String(""); + } + const QString href = format.anchorHref(); + if (!href.isEmpty()) { + html += QLatin1String(""); + closeAnchor = true; + } + } + + QString txt = fragment.text(); + const bool isObject = txt.contains(QChar::ObjectReplacementCharacter); + const bool isImage = isObject && format.isImageFormat(); + + QLatin1String styleTag(""); + else + html.chop(qstrlen(styleTag.latin1())); + + if (isObject) { + for (int i = 0; isImage && i < txt.length(); ++i) { + QTextImageFormat imgFmt = format.toImageFormat(); + + html += QLatin1String("(doc->objectForFormat(imgFmt))) + emitFloatStyle(imageFrame->frameFormat().position()); + + html += QLatin1String(" />"); + } + } else { + Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter)); + + txt = Qt::escape(txt); + + // split for [\n{LineSeparator}] + QString forcedLineBreakRegExp = QString::fromLatin1("[\\na]"); + forcedLineBreakRegExp[3] = QChar::LineSeparator; + + const QStringList lines = txt.split(QRegExp(forcedLineBreakRegExp)); + for (int i = 0; i < lines.count(); ++i) { + if (i > 0) + html += QLatin1String("
"); // space on purpose for compatibility with Netscape, Lynx & Co. + html += lines.at(i); + } + } + + if (attributesEmitted) + html += QLatin1String("
"); + + if (closeAnchor) + html += QLatin1String("
"); +} + +static bool isOrderedList(int style) +{ + return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha + || style == QTextListFormat::ListUpperAlpha + || style == QTextListFormat::ListUpperRoman + || style == QTextListFormat::ListLowerRoman + ; +} + +void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block) +{ + QTextBlockFormat format = block.blockFormat(); + emitAlignment(format.alignment()); + + // assume default to not bloat the html too much + // html += QLatin1String(" dir='ltr'"); + if (block.textDirection() == Qt::RightToLeft) + html += QLatin1String(" dir='rtl'"); + + QLatin1String style(" style=\""); + html += style; + + const bool emptyBlock = block.begin().atEnd(); + if (emptyBlock) { + html += QLatin1String("-qt-paragraph-type:empty;"); + } + + emitMargins(QString::number(format.topMargin()), + QString::number(format.bottomMargin()), + QString::number(format.leftMargin()), + QString::number(format.rightMargin())); + + html += QLatin1String(" -qt-block-indent:"); + html += QString::number(format.indent()); + html += QLatin1Char(';'); + + html += QLatin1String(" text-indent:"); + html += QString::number(format.textIndent()); + html += QLatin1String("px;"); + + if (block.userState() != -1) { + html += QLatin1String(" -qt-user-state:"); + html += QString::number(block.userState()); + html += QLatin1Char(';'); + } + + emitPageBreakPolicy(format.pageBreakPolicy()); + + QTextCharFormat diff; + if (emptyBlock) { // only print character properties when we don't expect them to be repeated by actual text in the parag + const QTextCharFormat blockCharFmt = block.charFormat(); + diff = formatDifference(defaultCharFormat, blockCharFmt).toCharFormat(); + } + + diff.clearProperty(QTextFormat::BackgroundBrush); + if (format.hasProperty(QTextFormat::BackgroundBrush)) { + QBrush bg = format.background(); + if (bg.style() != Qt::NoBrush) + diff.setProperty(QTextFormat::BackgroundBrush, format.property(QTextFormat::BackgroundBrush)); + } + + if (!diff.properties().isEmpty()) + emitCharFormatStyle(diff); + + html += QLatin1Char('"'); + +} + +void QTextHtmlExporter::emitBlock(const QTextBlock &block) +{ + if (block.begin().atEnd()) { + // ### HACK, remove once QTextFrame::Iterator is fixed + int p = block.position(); + if (p > 0) + --p; + QTextDocumentPrivate::FragmentIterator frag = doc->docHandle()->find(p); + QChar ch = doc->docHandle()->buffer().at(frag->stringPosition); + if (ch == QTextBeginningOfFrame + || ch == QTextEndOfFrame) + return; + } + + html += QLatin1Char('\n'); + + // save and later restore, in case we 'change' the default format by + // emitting block char format information + QTextCharFormat oldDefaultCharFormat = defaultCharFormat; + + QTextList *list = block.textList(); + if (list) { + if (list->itemNumber(block) == 0) { // first item? emit
    or appropriate + const QTextListFormat format = list->format(); + const int style = format.style(); + switch (style) { + case QTextListFormat::ListDecimal: html += QLatin1String(""); + } + + html += QLatin1String(""); + return; + } + + const bool pre = blockFormat.nonBreakableLines(); + if (pre) { + if (list) + html += QLatin1Char('>'); + html += QLatin1String("'); + + QTextBlock::Iterator it = block.begin(); + if (fragmentMarkers && !it.atEnd() && block == doc->begin()) + html += QLatin1String(""); + + for (; !it.atEnd(); ++it) + emitFragment(it.fragment()); + + if (fragmentMarkers && block.position() + block.length() == doc->docHandle()->length()) + html += QLatin1String(""); + + if (pre) + html += QLatin1String(""); + else if (list) + html += QLatin1String(""); + else + html += QLatin1String("

    "); + + if (list) { + if (list->itemNumber(block) == list->count() - 1) { // last item? close list + if (isOrderedList(list->format().style())) + html += QLatin1String(""); + else + html += QLatin1String("
"); + } + } + + defaultCharFormat = oldDefaultCharFormat; +} + +extern bool qHasPixmapTexture(const QBrush& brush); + +QString QTextHtmlExporter::findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap) +{ + QString url; + if (!doc) + return url; + + if (QTextDocument *parent = qobject_cast(doc->parent())) + return findUrlForImage(parent, cacheKey, isPixmap); + + if (doc && doc->docHandle()) { + QTextDocumentPrivate *priv = doc->docHandle(); + QMap::const_iterator it = priv->cachedResources.constBegin(); + for (; it != priv->cachedResources.constEnd(); ++it) { + + const QVariant &v = it.value(); + if (v.type() == QVariant::Image && !isPixmap) { + if (qvariant_cast(v).cacheKey() == cacheKey) + break; + } + + if (v.type() == QVariant::Pixmap && isPixmap) { + if (qvariant_cast(v).cacheKey() == cacheKey) + break; + } + } + + if (it != priv->cachedResources.constEnd()) + url = it.key().toString(); + } + + return url; +} + +void QTextDocumentPrivate::mergeCachedResources(const QTextDocumentPrivate *priv) +{ + if (!priv) + return; + + cachedResources.unite(priv->cachedResources); +} + +void QTextHtmlExporter::emitBackgroundAttribute(const QTextFormat &format) +{ + if (format.hasProperty(QTextFormat::BackgroundImageUrl)) { + QString url = format.property(QTextFormat::BackgroundImageUrl).toString(); + emitAttribute("background", url); + } else { + const QBrush &brush = format.background(); + if (brush.style() == Qt::SolidPattern) { + emitAttribute("bgcolor", brush.color().name()); + } else if (brush.style() == Qt::TexturePattern) { + const bool isPixmap = qHasPixmapTexture(brush); + const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey(); + + const QString url = findUrlForImage(doc, cacheKey, isPixmap); + + if (!url.isEmpty()) + emitAttribute("background", url); + } + } +} + +void QTextHtmlExporter::emitTable(const QTextTable *table) +{ + QTextTableFormat format = table->format(); + + html += QLatin1String("\n'); + + const int rows = table->rows(); + const int columns = table->columns(); + + QVector columnWidths = format.columnWidthConstraints(); + if (columnWidths.isEmpty()) { + columnWidths.resize(columns); + columnWidths.fill(QTextLength()); + } + Q_ASSERT(columnWidths.count() == columns); + + QVarLengthArray widthEmittedForColumn(columns); + for (int i = 0; i < columns; ++i) + widthEmittedForColumn[i] = false; + + const int headerRowCount = qMin(format.headerRowCount(), rows); + if (headerRowCount > 0) + html += QLatin1String(""); + + for (int row = 0; row < rows; ++row) { + html += QLatin1String("\n"); + + for (int col = 0; col < columns; ++col) { + const QTextTableCell cell = table->cellAt(row, col); + + // for col/rowspans + if (cell.row() != row) + continue; + + if (cell.column() != col) + continue; + + html += QLatin1String("\n 1) + emitAttribute("colspan", QString::number(cell.columnSpan())); + + if (cell.rowSpan() > 1) + emitAttribute("rowspan", QString::number(cell.rowSpan())); + + const QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); + emitBackgroundAttribute(cellFormat); + + QTextCharFormat oldDefaultCharFormat = defaultCharFormat; + + QTextCharFormat::VerticalAlignment valign = cellFormat.verticalAlignment(); + + QString styleString; + if (valign >= QTextCharFormat::AlignMiddle && valign <= QTextCharFormat::AlignBottom) { + styleString += QLatin1String(" vertical-align:"); + switch (valign) { + case QTextCharFormat::AlignMiddle: + styleString += QLatin1String("middle"); + break; + case QTextCharFormat::AlignTop: + styleString += QLatin1String("top"); + break; + case QTextCharFormat::AlignBottom: + styleString += QLatin1String("bottom"); + break; + default: + break; + } + styleString += QLatin1Char(';'); + + QTextCharFormat temp; + temp.setVerticalAlignment(valign); + defaultCharFormat.merge(temp); + } + + if (cellFormat.hasProperty(QTextFormat::TableCellLeftPadding)) + styleString += QLatin1String(" padding-left:") + QString::number(cellFormat.leftPadding()) + QLatin1Char(';'); + if (cellFormat.hasProperty(QTextFormat::TableCellRightPadding)) + styleString += QLatin1String(" padding-right:") + QString::number(cellFormat.rightPadding()) + QLatin1Char(';'); + if (cellFormat.hasProperty(QTextFormat::TableCellTopPadding)) + styleString += QLatin1String(" padding-top:") + QString::number(cellFormat.topPadding()) + QLatin1Char(';'); + if (cellFormat.hasProperty(QTextFormat::TableCellBottomPadding)) + styleString += QLatin1String(" padding-bottom:") + QString::number(cellFormat.bottomPadding()) + QLatin1Char(';'); + + if (!styleString.isEmpty()) + html += QLatin1String(" style=\"") + styleString + QLatin1Char('\"'); + + html += QLatin1Char('>'); + + emitFrame(cell.begin()); + + html += QLatin1String(""); + + defaultCharFormat = oldDefaultCharFormat; + } + + html += QLatin1String(""); + if (headerRowCount > 0 && row == headerRowCount - 1) + html += QLatin1String(""); + } + + html += QLatin1String(""); +} + +void QTextHtmlExporter::emitFrame(QTextFrame::Iterator frameIt) +{ + if (!frameIt.atEnd()) { + QTextFrame::Iterator next = frameIt; + ++next; + if (next.atEnd() + && frameIt.currentFrame() == 0 + && frameIt.parentFrame() != doc->rootFrame() + && frameIt.currentBlock().begin().atEnd()) + return; + } + + for (QTextFrame::Iterator it = frameIt; + !it.atEnd(); ++it) { + if (QTextFrame *f = it.currentFrame()) { + if (QTextTable *table = qobject_cast(f)) { + emitTable(table); + } else { + emitTextFrame(f); + } + } else if (it.currentBlock().isValid()) { + emitBlock(it.currentBlock()); + } + } +} + +void QTextHtmlExporter::emitTextFrame(const QTextFrame *f) +{ + FrameType frameType = f->parentFrame() ? TextFrame : RootFrame; + + html += QLatin1String("\nframeFormat(); + + if (format.hasProperty(QTextFormat::FrameBorder)) + emitAttribute("border", QString::number(format.border())); + + emitFrameStyle(format, frameType); + + emitTextLength("width", format.width()); + emitTextLength("height", format.height()); + + // root frame's bcolor goes in the tag + if (frameType != RootFrame) + emitBackgroundAttribute(format); + + html += QLatin1Char('>'); + html += QLatin1String("\n\n"); + emitFrame(f->begin()); + html += QLatin1String(""); +} + +void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType frameType) +{ + QLatin1String styleAttribute(" style=\""); + html += styleAttribute; + const int originalHtmlLength = html.length(); + + if (frameType == TextFrame) + html += QLatin1String("-qt-table-type: frame;"); + else if (frameType == RootFrame) + html += QLatin1String("-qt-table-type: root;"); + + const QTextFrameFormat defaultFormat; + + emitFloatStyle(format.position(), OmitStyleTag); + emitPageBreakPolicy(format.pageBreakPolicy()); + + if (format.borderBrush() != defaultFormat.borderBrush()) { + html += QLatin1String(" border-color:"); + html += format.borderBrush().color().name(); + html += QLatin1Char(';'); + } + + if (format.borderStyle() != defaultFormat.borderStyle()) + emitBorderStyle(format.borderStyle()); + + if (format.hasProperty(QTextFormat::FrameMargin) + || format.hasProperty(QTextFormat::FrameLeftMargin) + || format.hasProperty(QTextFormat::FrameRightMargin) + || format.hasProperty(QTextFormat::FrameTopMargin) + || format.hasProperty(QTextFormat::FrameBottomMargin)) + emitMargins(QString::number(format.topMargin()), + QString::number(format.bottomMargin()), + QString::number(format.leftMargin()), + QString::number(format.rightMargin())); + + if (html.length() == originalHtmlLength) // nothing emitted? + html.chop(qstrlen(styleAttribute.latin1())); + else + html += QLatin1Char('\"'); +} + +/*! + Returns a string containing an HTML representation of the document. + + The \a encoding parameter specifies the value for the charset attribute + in the html header. For example if 'utf-8' is specified then the + beginning of the generated html will look like this: + \snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 1 + + If no encoding is specified then no such meta information is generated. + + If you later on convert the returned html string into a byte array for + transmission over a network or when saving to disk you should specify + the encoding you're going to use for the conversion to a byte array here. + + \sa {Supported HTML Subset} +*/ +#ifndef QT_NO_TEXTHTMLPARSER +QString QTextDocument::toHtml(const QByteArray &encoding) const +{ + return QTextHtmlExporter(this).toHtml(encoding); +} +#endif // QT_NO_TEXTHTMLPARSER + +/*! + Returns a vector of text formats for all the formats used in the document. +*/ +QVector QTextDocument::allFormats() const +{ + Q_D(const QTextDocument); + return d->formatCollection()->formats; +} + + +/*! + \internal + + So that not all classes have to be friends of each other... +*/ +QTextDocumentPrivate *QTextDocument::docHandle() const +{ + Q_D(const QTextDocument); + return const_cast(d); +} + +/*! + \since 4.4 + \fn QTextDocument::undoCommandAdded() + + This signal is emitted every time a new level of undo is added to the QTextDocument. +*/ + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextdocument.h b/src/gui/text/qtextdocument.h new file mode 100644 index 0000000000..f87ccc91e8 --- /dev/null +++ b/src/gui/text/qtextdocument.h @@ -0,0 +1,309 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTDOCUMENT_H +#define QTEXTDOCUMENT_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QTextFormatCollection; +class QTextListFormat; +class QRect; +class QPainter; +class QPrinter; +class QAbstractTextDocumentLayout; +class QPoint; +class QTextCursor; +class QTextObject; +class QTextFormat; +class QTextFrame; +class QTextBlock; +class QTextCodec; +class QUrl; +class QVariant; +class QRectF; +class QTextOption; + +template class QVector; + +namespace Qt +{ + enum HitTestAccuracy { ExactHit, FuzzyHit }; + enum WhiteSpaceMode { + WhiteSpaceNormal, + WhiteSpacePre, + WhiteSpaceNoWrap, + WhiteSpaceModeUndefined = -1 + }; + + Q_GUI_EXPORT bool mightBeRichText(const QString&); + Q_GUI_EXPORT QString escape(const QString& plain); + Q_GUI_EXPORT QString convertFromPlainText(const QString &plain, WhiteSpaceMode mode = WhiteSpacePre); + +#ifndef QT_NO_TEXTCODEC + Q_GUI_EXPORT QTextCodec *codecForHtml(const QByteArray &ba); +#endif +} + +class Q_GUI_EXPORT QAbstractUndoItem +{ +public: + virtual ~QAbstractUndoItem() = 0; + virtual void undo() = 0; + virtual void redo() = 0; +}; + +inline QAbstractUndoItem::~QAbstractUndoItem() +{ +} + +class QTextDocumentPrivate; + +class Q_GUI_EXPORT QTextDocument : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool undoRedoEnabled READ isUndoRedoEnabled WRITE setUndoRedoEnabled) + Q_PROPERTY(bool modified READ isModified WRITE setModified DESIGNABLE false) + Q_PROPERTY(QSizeF pageSize READ pageSize WRITE setPageSize) + Q_PROPERTY(QFont defaultFont READ defaultFont WRITE setDefaultFont) + Q_PROPERTY(bool useDesignMetrics READ useDesignMetrics WRITE setUseDesignMetrics) + Q_PROPERTY(QSizeF size READ size) + Q_PROPERTY(qreal textWidth READ textWidth WRITE setTextWidth) + Q_PROPERTY(int blockCount READ blockCount) + Q_PROPERTY(qreal indentWidth READ indentWidth WRITE setIndentWidth) +#ifndef QT_NO_CSSPARSER + Q_PROPERTY(QString defaultStyleSheet READ defaultStyleSheet WRITE setDefaultStyleSheet) +#endif + Q_PROPERTY(int maximumBlockCount READ maximumBlockCount WRITE setMaximumBlockCount) + Q_PROPERTY(qreal documentMargin READ documentMargin WRITE setDocumentMargin) + QDOC_PROPERTY(QTextOption defaultTextOption READ defaultTextOption WRITE setDefaultTextOption) + +public: + explicit QTextDocument(QObject *parent = 0); + explicit QTextDocument(const QString &text, QObject *parent = 0); + ~QTextDocument(); + + QTextDocument *clone(QObject *parent = 0) const; + + bool isEmpty() const; + virtual void clear(); + + void setUndoRedoEnabled(bool enable); + bool isUndoRedoEnabled() const; + + bool isUndoAvailable() const; + bool isRedoAvailable() const; + + int availableUndoSteps() const; + int availableRedoSteps() const; + + int revision() const; + + void setDocumentLayout(QAbstractTextDocumentLayout *layout); + QAbstractTextDocumentLayout *documentLayout() const; + + enum MetaInformation { + DocumentTitle, + DocumentUrl + }; + void setMetaInformation(MetaInformation info, const QString &); + QString metaInformation(MetaInformation info) const; + +#ifndef QT_NO_TEXTHTMLPARSER + QString toHtml(const QByteArray &encoding = QByteArray()) const; + void setHtml(const QString &html); +#endif + + QString toPlainText() const; + void setPlainText(const QString &text); + + QChar characterAt(int pos) const; + + enum FindFlag + { + FindBackward = 0x00001, + FindCaseSensitively = 0x00002, + FindWholeWords = 0x00004 + }; + Q_DECLARE_FLAGS(FindFlags, FindFlag) + + QTextCursor find(const QString &subString, int from = 0, FindFlags options = 0) const; + QTextCursor find(const QString &subString, const QTextCursor &from, FindFlags options = 0) const; + + QTextCursor find(const QRegExp &expr, int from = 0, FindFlags options = 0) const; + QTextCursor find(const QRegExp &expr, const QTextCursor &from, FindFlags options = 0) const; + + QTextFrame *frameAt(int pos) const; + QTextFrame *rootFrame() const; + + QTextObject *object(int objectIndex) const; + QTextObject *objectForFormat(const QTextFormat &) const; + + QTextBlock findBlock(int pos) const; + QTextBlock findBlockByNumber(int blockNumber) const; + QTextBlock findBlockByLineNumber(int blockNumber) const; + QTextBlock begin() const; + QTextBlock end() const; + + QTextBlock firstBlock() const; + QTextBlock lastBlock() const; + + void setPageSize(const QSizeF &size); + QSizeF pageSize() const; + + void setDefaultFont(const QFont &font); + QFont defaultFont() const; + + int pageCount() const; + + bool isModified() const; + +#ifndef QT_NO_PRINTER + void print(QPrinter *printer) const; +#endif + + enum ResourceType { + HtmlResource = 1, + ImageResource = 2, + StyleSheetResource = 3, + + UserResource = 100 + }; + + QVariant resource(int type, const QUrl &name) const; + void addResource(int type, const QUrl &name, const QVariant &resource); + + QVector allFormats() const; + + void markContentsDirty(int from, int length); + + void setUseDesignMetrics(bool b); + bool useDesignMetrics() const; + + void drawContents(QPainter *painter, const QRectF &rect = QRectF()); + + void setTextWidth(qreal width); + qreal textWidth() const; + + qreal idealWidth() const; + + qreal indentWidth() const; + void setIndentWidth(qreal width); + + qreal documentMargin() const; + void setDocumentMargin(qreal margin); + + void adjustSize(); + QSizeF size() const; + + int blockCount() const; + int lineCount() const; + int characterCount() const; + +#ifndef QT_NO_CSSPARSER + void setDefaultStyleSheet(const QString &sheet); + QString defaultStyleSheet() const; +#endif + + void undo(QTextCursor *cursor); + void redo(QTextCursor *cursor); + + enum Stacks { + UndoStack = 0x01, + RedoStack = 0x02, + UndoAndRedoStacks = UndoStack | RedoStack + }; + void clearUndoRedoStacks(Stacks historyToClear = UndoAndRedoStacks); + + int maximumBlockCount() const; + void setMaximumBlockCount(int maximum); + + QTextOption defaultTextOption() const; + void setDefaultTextOption(const QTextOption &option); + +Q_SIGNALS: + void contentsChange(int from, int charsRemoves, int charsAdded); + void contentsChanged(); + void undoAvailable(bool); + void redoAvailable(bool); + void undoCommandAdded(); + void modificationChanged(bool m); + void cursorPositionChanged(const QTextCursor &cursor); + void blockCountChanged(int newBlockCount); + + void documentLayoutChanged(); + +public Q_SLOTS: + void undo(); + void redo(); + void appendUndoItem(QAbstractUndoItem *); + void setModified(bool m = true); + +protected: + virtual QTextObject *createObject(const QTextFormat &f); + virtual QVariant loadResource(int type, const QUrl &name); + + QTextDocument(QTextDocumentPrivate &dd, QObject *parent); +public: + QTextDocumentPrivate *docHandle() const; +private: + Q_DISABLE_COPY(QTextDocument) + Q_DECLARE_PRIVATE(QTextDocument) + friend class QTextObjectPrivate; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QTextDocument::FindFlags) + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTEXTDOCUMENT_H diff --git a/src/gui/text/qtextdocument_p.cpp b/src/gui/text/qtextdocument_p.cpp new file mode 100644 index 0000000000..a997720c12 --- /dev/null +++ b/src/gui/text/qtextdocument_p.cpp @@ -0,0 +1,1724 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "qtextdocument_p.h" +#include "qtextdocument.h" +#include +#include "qtextformat_p.h" +#include "qtextobject_p.h" +#include "qtextcursor.h" +#include "qtextimagehandler_p.h" +#include "qtextcursor_p.h" +#include "qtextdocumentlayout_p.h" +#include "qtexttable.h" +#include "qtextengine_p.h" + +#include + +QT_BEGIN_NAMESPACE + +#define PMDEBUG if(0) qDebug + +// The VxWorks DIAB compiler crashes when initializing the anonymouse union with { a7 } +#if !defined(Q_CC_DIAB) +# define QT_INIT_TEXTUNDOCOMMAND(c, a1, a2, a3, a4, a5, a6, a7, a8) \ + QTextUndoCommand c = { a1, a2, 0, 0, quint8(a3), a4, a5, a6, { a7 }, a8 } +#else +# define QT_INIT_TEXTUNDOCOMMAND(c, a1, a2, a3, a4, a5, a6, a7, a8) \ + QTextUndoCommand c = { a1, a2, 0, 0, a3, a4, a5, a6 }; c.blockFormat = a7; c.revision = a8 +#endif + +/* + Structure of a document: + + DOCUMENT :== FRAME_CONTENTS + FRAME :== START_OF_FRAME FRAME_CONTENTS END_OF_FRAME + FRAME_CONTENTS = LIST_OF_BLOCKS ((FRAME | TABLE) LIST_OF_BLOCKS)* + TABLE :== (START_OF_FRAME TABLE_CELL)+ END_OF_FRAME + TABLE_CELL = FRAME_CONTENTS + LIST_OF_BLOCKS :== (BLOCK END_OF_PARA)* BLOCK + BLOCK :== (FRAGMENT)* + FRAGMENT :== String of characters + + END_OF_PARA :== 0x2029 # Paragraph separator in Unicode + START_OF_FRAME :== 0xfdd0 + END_OF_FRAME := 0xfdd1 + + Note also that LIST_OF_BLOCKS can be empty. Nevertheless, there is + at least one valid cursor position there where you could start + typing. The block format is in this case determined by the last + END_OF_PARA/START_OF_FRAME/END_OF_FRAME (see below). + + Lists are not in here, as they are treated specially. A list is just + a collection of (not necessarily connected) blocks, that share the + same objectIndex() in the format that refers to the list format and + object. + + The above does not clearly note where formats are. Here's + how it looks currently: + + FRAGMENT: one charFormat associated + + END_OF_PARA: one charFormat, and a blockFormat for the _next_ block. + + START_OF_FRAME: one char format, and a blockFormat (for the next + block). The format associated with the objectIndex() of the + charFormat decides whether this is a frame or table and its + properties + + END_OF_FRAME: one charFormat and a blockFormat (for the next + block). The object() of the charFormat is the same as for the + corresponding START_OF_BLOCK. + + + The document is independent of the layout with certain restrictions: + + * Cursor movement (esp. up and down) depend on the layout. + * You cannot have more than one layout, as the layout data of QTextObjects + is stored in the text object itself. + +*/ + +void QTextBlockData::invalidate() const +{ + if (layout) + layout->engine()->invalidate(); +} + +static bool isValidBlockSeparator(const QChar &ch) +{ + return ch == QChar::ParagraphSeparator + || ch == QTextBeginningOfFrame + || ch == QTextEndOfFrame; +} + +#ifndef QT_NO_DEBUG +static bool noBlockInString(const QString &str) +{ + return !str.contains(QChar::ParagraphSeparator) + && !str.contains(QTextBeginningOfFrame) + && !str.contains(QTextEndOfFrame); +} +#endif + +bool QTextUndoCommand::tryMerge(const QTextUndoCommand &other) +{ + if (command != other.command) + return false; + + if (command == Inserted + && (pos + length == other.pos) + && (strPos + length == other.strPos) + && format == other.format) { + + length += other.length; + return true; + } + + // removal to the 'right' using 'Delete' key + if (command == Removed + && pos == other.pos + && (strPos + length == other.strPos) + && format == other.format) { + + length += other.length; + return true; + } + + // removal to the 'left' using 'Backspace' + if (command == Removed + && (other.pos + other.length == pos) + && (other.strPos + other.length == strPos) + && (format == other.format)) { + + int l = length; + (*this) = other; + + length += l; + return true; + } + + return false; +} + +QTextDocumentPrivate::QTextDocumentPrivate() + : wasUndoAvailable(false), + wasRedoAvailable(false), + docChangeOldLength(0), + docChangeLength(0), + framesDirty(true), + rtFrame(0), + initialBlockCharFormatIndex(-1) // set correctly later in init() +{ + editBlock = 0; + editBlockCursorPosition = -1; + docChangeFrom = -1; + + undoState = 0; + revision = -1; // init() inserts a block, bringing it to 0 + + lout = 0; + + modified = false; + modifiedState = 0; + + undoEnabled = true; + inContentsChange = false; + blockCursorAdjustment = false; + + defaultTextOption.setTabStop(80); // same as in qtextengine.cpp + defaultTextOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + + indentWidth = 40; + documentMargin = 4; + + maximumBlockCount = 0; + needsEnsureMaximumBlockCount = false; + unreachableCharacterCount = 0; + lastBlockCount = 0; +} + +void QTextDocumentPrivate::init() +{ + framesDirty = false; + + bool undoState = undoEnabled; + undoEnabled = false; + initialBlockCharFormatIndex = formats.indexForFormat(QTextCharFormat()); + insertBlock(0, formats.indexForFormat(QTextBlockFormat()), formats.indexForFormat(QTextCharFormat())); + undoEnabled = undoState; + modified = false; + modifiedState = 0; +} + +void QTextDocumentPrivate::clear() +{ + Q_Q(QTextDocument); + + foreach (QTextCursorPrivate *curs, cursors) { + curs->setPosition(0); + curs->currentCharFormat = -1; + curs->anchor = 0; + curs->adjusted_anchor = 0; + } + + QListoldCursors = cursors; + QT_TRY{ + cursors.clear(); + + QMap::Iterator objectIt = objects.begin(); + while (objectIt != objects.end()) { + if (*objectIt != rtFrame) { + delete *objectIt; + objectIt = objects.erase(objectIt); + } else { + ++objectIt; + } + } + // also clear out the remaining root frame pointer + // (we're going to delete the object further down) + objects.clear(); + + title.clear(); + clearUndoRedoStacks(QTextDocument::UndoAndRedoStacks); + text = QString(); + unreachableCharacterCount = 0; + modifiedState = 0; + modified = false; + formats = QTextFormatCollection(); + int len = fragments.length(); + fragments.clear(); + blocks.clear(); + cachedResources.clear(); + delete rtFrame; + rtFrame = 0; + init(); + cursors = oldCursors; + inContentsChange = true; + q->contentsChange(0, len, 0); + inContentsChange = false; + if (lout) + lout->documentChanged(0, len, 0); + } QT_CATCH(...) { + cursors = oldCursors; // at least recover the cursors + QT_RETHROW; + } +} + +QTextDocumentPrivate::~QTextDocumentPrivate() +{ + foreach (QTextCursorPrivate *curs, cursors) + curs->priv = 0; + cursors.clear(); + undoState = 0; + undoEnabled = true; + clearUndoRedoStacks(QTextDocument::RedoStack); +} + +void QTextDocumentPrivate::setLayout(QAbstractTextDocumentLayout *layout) +{ + Q_Q(QTextDocument); + if (lout == layout) + return; + const bool firstLayout = !lout; + delete lout; + lout = layout; + + if (!firstLayout) + for (BlockMap::Iterator it = blocks.begin(); !it.atEnd(); ++it) + it->free(); + + emit q->documentLayoutChanged(); + inContentsChange = true; + emit q->contentsChange(0, 0, length()); + inContentsChange = false; + if (lout) + lout->documentChanged(0, 0, length()); +} + + +void QTextDocumentPrivate::insert_string(int pos, uint strPos, uint length, int format, QTextUndoCommand::Operation op) +{ + // ##### optimize when only appending to the fragment! + Q_ASSERT(noBlockInString(text.mid(strPos, length))); + + split(pos); + uint x = fragments.insert_single(pos, length); + QTextFragmentData *X = fragments.fragment(x); + X->format = format; + X->stringPosition = strPos; + uint w = fragments.previous(x); + if (w) + unite(w); + + int b = blocks.findNode(pos); + blocks.setSize(b, blocks.size(b)+length); + + Q_ASSERT(blocks.length() == fragments.length()); + + QTextFrame *frame = qobject_cast(objectForFormat(format)); + if (frame) { + frame->d_func()->fragmentAdded(text.at(strPos), x); + framesDirty = true; + } + + adjustDocumentChangesAndCursors(pos, length, op); +} + +int QTextDocumentPrivate::insert_block(int pos, uint strPos, int format, int blockFormat, QTextUndoCommand::Operation op, int command) +{ + split(pos); + uint x = fragments.insert_single(pos, 1); + QTextFragmentData *X = fragments.fragment(x); + X->format = format; + X->stringPosition = strPos; + // no need trying to unite, since paragraph separators are always in a fragment of their own + + Q_ASSERT(isValidBlockSeparator(text.at(strPos))); + Q_ASSERT(blocks.length()+1 == fragments.length()); + + int block_pos = pos; + if (blocks.length() && command == QTextUndoCommand::BlockRemoved) + ++block_pos; + int size = 1; + int n = blocks.findNode(block_pos); + int key = n ? blocks.position(n) : blocks.length(); + + Q_ASSERT(n || (!n && block_pos == blocks.length())); + if (key != block_pos) { + Q_ASSERT(key < block_pos); + int oldSize = blocks.size(n); + blocks.setSize(n, block_pos-key); + size += oldSize - (block_pos-key); + } + int b = blocks.insert_single(block_pos, size); + QTextBlockData *B = blocks.fragment(b); + B->format = blockFormat; + + Q_ASSERT(blocks.length() == fragments.length()); + + QTextBlockGroup *group = qobject_cast(objectForFormat(blockFormat)); + if (group) + group->blockInserted(QTextBlock(this, b)); + + QTextFrame *frame = qobject_cast(objectForFormat(formats.format(format))); + if (frame) { + frame->d_func()->fragmentAdded(text.at(strPos), x); + framesDirty = true; + } + + adjustDocumentChangesAndCursors(pos, 1, op); + return x; +} + +int QTextDocumentPrivate::insertBlock(const QChar &blockSeparator, + int pos, int blockFormat, int charFormat, QTextUndoCommand::Operation op) +{ + Q_ASSERT(formats.format(blockFormat).isBlockFormat()); + Q_ASSERT(formats.format(charFormat).isCharFormat()); + Q_ASSERT(pos >= 0 && (pos < fragments.length() || (pos == 0 && fragments.length() == 0))); + Q_ASSERT(isValidBlockSeparator(blockSeparator)); + + beginEditBlock(); + + int strPos = text.length(); + text.append(blockSeparator); + + int ob = blocks.findNode(pos); + bool atBlockEnd = true; + bool atBlockStart = true; + int oldRevision = 0; + if (ob) { + atBlockEnd = (pos - blocks.position(ob) == blocks.size(ob)-1); + atBlockStart = ((int)blocks.position(ob) == pos); + oldRevision = blocks.fragment(ob)->revision; + } + + const int fragment = insert_block(pos, strPos, charFormat, blockFormat, op, QTextUndoCommand::BlockRemoved); + + Q_ASSERT(blocks.length() == fragments.length()); + + int b = blocks.findNode(pos); + QTextBlockData *B = blocks.fragment(b); + + QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::BlockInserted, (editBlock != 0), + op, charFormat, strPos, pos, blockFormat, + B->revision); + + appendUndoItem(c); + Q_ASSERT(undoState == undoStack.size()); + + // update revision numbers of the modified blocks. + B->revision = (atBlockEnd && !atBlockStart)? oldRevision : revision; + b = blocks.next(b); + if (b) { + B = blocks.fragment(b); + B->revision = atBlockStart ? oldRevision : revision; + } + + if (formats.charFormat(charFormat).objectIndex() == -1) + needsEnsureMaximumBlockCount = true; + + endEditBlock(); + return fragment; +} + +int QTextDocumentPrivate::insertBlock(int pos, int blockFormat, int charFormat, QTextUndoCommand::Operation op) +{ + return insertBlock(QChar::ParagraphSeparator, pos, blockFormat, charFormat, op); +} + +void QTextDocumentPrivate::insert(int pos, int strPos, int strLength, int format) +{ + if (strLength <= 0) + return; + + Q_ASSERT(pos >= 0 && pos < fragments.length()); + Q_ASSERT(formats.format(format).isCharFormat()); + + insert_string(pos, strPos, strLength, format, QTextUndoCommand::MoveCursor); + if (undoEnabled) { + int b = blocks.findNode(pos); + QTextBlockData *B = blocks.fragment(b); + + QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::Inserted, (editBlock != 0), + QTextUndoCommand::MoveCursor, format, strPos, pos, strLength, + B->revision); + appendUndoItem(c); + B->revision = revision; + Q_ASSERT(undoState == undoStack.size()); + } + finishEdit(); +} + +void QTextDocumentPrivate::insert(int pos, const QString &str, int format) +{ + if (str.size() == 0) + return; + + Q_ASSERT(noBlockInString(str)); + + int strPos = text.length(); + text.append(str); + insert(pos, strPos, str.length(), format); +} + +int QTextDocumentPrivate::remove_string(int pos, uint length, QTextUndoCommand::Operation op) +{ + Q_ASSERT(pos >= 0); + Q_ASSERT(blocks.length() == fragments.length()); + Q_ASSERT(blocks.length() >= pos+(int)length); + + int b = blocks.findNode(pos); + uint x = fragments.findNode(pos); + + Q_ASSERT(blocks.size(b) > length); + Q_ASSERT(x && fragments.position(x) == (uint)pos && fragments.size(x) == length); + Q_ASSERT(noBlockInString(text.mid(fragments.fragment(x)->stringPosition, length))); + + blocks.setSize(b, blocks.size(b)-length); + + QTextFrame *frame = qobject_cast(objectForFormat(fragments.fragment(x)->format)); + if (frame) { + frame->d_func()->fragmentRemoved(text.at(fragments.fragment(x)->stringPosition), x); + framesDirty = true; + } + + const int w = fragments.erase_single(x); + + if (!undoEnabled) + unreachableCharacterCount += length; + + adjustDocumentChangesAndCursors(pos, -int(length), op); + + return w; +} + +int QTextDocumentPrivate::remove_block(int pos, int *blockFormat, int command, QTextUndoCommand::Operation op) +{ + Q_ASSERT(pos >= 0); + Q_ASSERT(blocks.length() == fragments.length()); + Q_ASSERT(blocks.length() > pos); + + int b = blocks.findNode(pos); + uint x = fragments.findNode(pos); + + Q_ASSERT(x && (int)fragments.position(x) == pos); + Q_ASSERT(fragments.size(x) == 1); + Q_ASSERT(isValidBlockSeparator(text.at(fragments.fragment(x)->stringPosition))); + Q_ASSERT(b); + + if (blocks.size(b) == 1 && command == QTextUndoCommand::BlockAdded) { + Q_ASSERT((int)blocks.position(b) == pos); +// qDebug("removing empty block"); + // empty block remove the block itself + } else { + // non empty block, merge with next one into this block +// qDebug("merging block with next"); + int n = blocks.next(b); + Q_ASSERT((int)blocks.position(n) == pos + 1); + blocks.setSize(b, blocks.size(b) + blocks.size(n) - 1); + b = n; + } + *blockFormat = blocks.fragment(b)->format; + + QTextBlockGroup *group = qobject_cast(objectForFormat(blocks.fragment(b)->format)); + if (group) + group->blockRemoved(QTextBlock(this, b)); + + QTextFrame *frame = qobject_cast(objectForFormat(fragments.fragment(x)->format)); + if (frame) { + frame->d_func()->fragmentRemoved(text.at(fragments.fragment(x)->stringPosition), x); + framesDirty = true; + } + + blocks.erase_single(b); + const int w = fragments.erase_single(x); + + adjustDocumentChangesAndCursors(pos, -1, op); + + return w; +} + +#if !defined(QT_NO_DEBUG) +static bool isAncestorFrame(QTextFrame *possibleAncestor, QTextFrame *child) +{ + while (child) { + if (child == possibleAncestor) + return true; + child = child->parentFrame(); + } + return false; +} +#endif + +void QTextDocumentPrivate::move(int pos, int to, int length, QTextUndoCommand::Operation op) +{ + Q_ASSERT(to <= fragments.length() && to <= pos); + Q_ASSERT(pos >= 0 && pos+length <= fragments.length()); + Q_ASSERT(blocks.length() == fragments.length()); + + if (pos == to) + return; + + const bool needsInsert = to != -1; + +#if !defined(QT_NO_DEBUG) + const bool startAndEndInSameFrame = (frameAt(pos) == frameAt(pos + length - 1)); + + const bool endIsEndOfChildFrame = (isAncestorFrame(frameAt(pos), frameAt(pos + length - 1)) + && text.at(find(pos + length - 1)->stringPosition) == QTextEndOfFrame); + + const bool startIsStartOfFrameAndEndIsEndOfFrameWithCommonParent + = (text.at(find(pos)->stringPosition) == QTextBeginningOfFrame + && text.at(find(pos + length - 1)->stringPosition) == QTextEndOfFrame + && frameAt(pos)->parentFrame() == frameAt(pos + length - 1)->parentFrame()); + + const bool isFirstTableCell = (qobject_cast(frameAt(pos + length - 1)) + && frameAt(pos + length - 1)->parentFrame() == frameAt(pos)); + + Q_ASSERT(startAndEndInSameFrame || endIsEndOfChildFrame || startIsStartOfFrameAndEndIsEndOfFrameWithCommonParent || isFirstTableCell); +#endif + + split(pos); + split(pos+length); + + uint dst = needsInsert ? fragments.findNode(to) : 0; + uint dstKey = needsInsert ? fragments.position(dst) : 0; + + uint x = fragments.findNode(pos); + uint end = fragments.findNode(pos+length); + + uint w = 0; + while (x != end) { + uint n = fragments.next(x); + + uint key = fragments.position(x); + uint b = blocks.findNode(key+1); + QTextBlockData *B = blocks.fragment(b); + int blockRevision = B->revision; + + QTextFragmentData *X = fragments.fragment(x); + QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::Removed, (editBlock != 0), + op, X->format, X->stringPosition, key, X->size_array[0], + blockRevision); + QT_INIT_TEXTUNDOCOMMAND(cInsert, QTextUndoCommand::Inserted, (editBlock != 0), + op, X->format, X->stringPosition, dstKey, X->size_array[0], + blockRevision); + + if (key+1 != blocks.position(b)) { +// qDebug("remove_string from %d length %d", key, X->size_array[0]); + Q_ASSERT(noBlockInString(text.mid(X->stringPosition, X->size_array[0]))); + w = remove_string(key, X->size_array[0], op); + + if (needsInsert) { + insert_string(dstKey, X->stringPosition, X->size_array[0], X->format, op); + dstKey += X->size_array[0]; + } + } else { +// qDebug("remove_block at %d", key); + Q_ASSERT(X->size_array[0] == 1 && isValidBlockSeparator(text.at(X->stringPosition))); + b = blocks.previous(b); + B = 0; + c.command = blocks.size(b) == 1 ? QTextUndoCommand::BlockDeleted : QTextUndoCommand::BlockRemoved; + w = remove_block(key, &c.blockFormat, QTextUndoCommand::BlockAdded, op); + + if (needsInsert) { + insert_block(dstKey++, X->stringPosition, X->format, c.blockFormat, op, QTextUndoCommand::BlockRemoved); + cInsert.command = blocks.size(b) == 1 ? QTextUndoCommand::BlockAdded : QTextUndoCommand::BlockInserted; + cInsert.blockFormat = c.blockFormat; + } + } + appendUndoItem(c); + if (B) + B->revision = revision; + x = n; + + if (needsInsert) + appendUndoItem(cInsert); + } + if (w) + unite(w); + + Q_ASSERT(blocks.length() == fragments.length()); + + if (!blockCursorAdjustment) + finishEdit(); +} + +void QTextDocumentPrivate::remove(int pos, int length, QTextUndoCommand::Operation op) +{ + if (length == 0) + return; + blockCursorAdjustment = true; + move(pos, -1, length, op); + blockCursorAdjustment = false; + foreach (QTextCursorPrivate *curs, cursors) { + if (curs->adjustPosition(pos, -length, op) == QTextCursorPrivate::CursorMoved) { + curs->changed = true; + } + } + finishEdit(); +} + +void QTextDocumentPrivate::setCharFormat(int pos, int length, const QTextCharFormat &newFormat, FormatChangeMode mode) +{ + beginEditBlock(); + + Q_ASSERT(newFormat.isValid()); + + int newFormatIdx = -1; + if (mode == SetFormatAndPreserveObjectIndices) { + QTextCharFormat cleanFormat = newFormat; + cleanFormat.clearProperty(QTextFormat::ObjectIndex); + newFormatIdx = formats.indexForFormat(cleanFormat); + } else if (mode == SetFormat) { + newFormatIdx = formats.indexForFormat(newFormat); + } + + if (pos == -1) { + if (mode == MergeFormat) { + QTextFormat format = formats.format(initialBlockCharFormatIndex); + format.merge(newFormat); + initialBlockCharFormatIndex = formats.indexForFormat(format); + } else if (mode == SetFormatAndPreserveObjectIndices + && formats.format(initialBlockCharFormatIndex).objectIndex() != -1) { + QTextCharFormat f = newFormat; + f.setObjectIndex(formats.format(initialBlockCharFormatIndex).objectIndex()); + initialBlockCharFormatIndex = formats.indexForFormat(f); + } else { + initialBlockCharFormatIndex = newFormatIdx; + } + + ++pos; + --length; + } + + const int startPos = pos; + const int endPos = pos + length; + + split(startPos); + split(endPos); + + while (pos < endPos) { + FragmentMap::Iterator it = fragments.find(pos); + Q_ASSERT(!it.atEnd()); + + QTextFragmentData *fragment = it.value(); + + Q_ASSERT(formats.format(fragment->format).type() == QTextFormat::CharFormat); + + int offset = pos - it.position(); + int length = qMin(endPos - pos, int(fragment->size_array[0] - offset)); + int oldFormat = fragment->format; + + if (mode == MergeFormat) { + QTextFormat format = formats.format(fragment->format); + format.merge(newFormat); + fragment->format = formats.indexForFormat(format); + } else if (mode == SetFormatAndPreserveObjectIndices + && formats.format(oldFormat).objectIndex() != -1) { + QTextCharFormat f = newFormat; + f.setObjectIndex(formats.format(oldFormat).objectIndex()); + fragment->format = formats.indexForFormat(f); + } else { + fragment->format = newFormatIdx; + } + + QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::CharFormatChanged, true, QTextUndoCommand::MoveCursor, oldFormat, + 0, pos, length, 0); + appendUndoItem(c); + + pos += length; + Q_ASSERT(pos == (int)(it.position() + fragment->size_array[0]) || pos >= endPos); + } + + int n = fragments.findNode(startPos - 1); + if (n) + unite(n); + + n = fragments.findNode(endPos); + if (n) + unite(n); + + QTextBlock blockIt = blocksFind(startPos); + QTextBlock endIt = blocksFind(endPos); + if (endIt.isValid()) + endIt = endIt.next(); + for (; blockIt.isValid() && blockIt != endIt; blockIt = blockIt.next()) + QTextDocumentPrivate::block(blockIt)->invalidate(); + + documentChange(startPos, length); + + endEditBlock(); +} + +void QTextDocumentPrivate::setBlockFormat(const QTextBlock &from, const QTextBlock &to, + const QTextBlockFormat &newFormat, FormatChangeMode mode) +{ + beginEditBlock(); + + Q_ASSERT(mode != SetFormatAndPreserveObjectIndices); // only implemented for setCharFormat + + Q_ASSERT(newFormat.isValid()); + + int newFormatIdx = -1; + if (mode == SetFormat) + newFormatIdx = formats.indexForFormat(newFormat); + QTextBlockGroup *group = qobject_cast(objectForFormat(newFormat)); + + QTextBlock it = from; + QTextBlock end = to; + if (end.isValid()) + end = end.next(); + + for (; it != end; it = it.next()) { + int oldFormat = block(it)->format; + QTextBlockFormat format = formats.blockFormat(oldFormat); + QTextBlockGroup *oldGroup = qobject_cast(objectForFormat(format)); + if (mode == MergeFormat) { + format.merge(newFormat); + newFormatIdx = formats.indexForFormat(format); + group = qobject_cast(objectForFormat(format)); + } + block(it)->format = newFormatIdx; + + block(it)->invalidate(); + + QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::BlockFormatChanged, true, QTextUndoCommand::MoveCursor, oldFormat, + 0, it.position(), 1, 0); + appendUndoItem(c); + + if (group != oldGroup) { + if (oldGroup) + oldGroup->blockRemoved(it); + if (group) + group->blockInserted(it); + } else if (group) { + group->blockFormatChanged(it); + } + } + + documentChange(from.position(), to.position() + to.length() - from.position()); + + endEditBlock(); +} + + +bool QTextDocumentPrivate::split(int pos) +{ + uint x = fragments.findNode(pos); + if (x) { + int k = fragments.position(x); +// qDebug("found fragment with key %d, size_left=%d, size=%d to split at %d", +// k, (*it)->size_left[0], (*it)->size_array[0], pos); + if (k != pos) { + Q_ASSERT(k <= pos); + // need to resize the first fragment and add a new one + QTextFragmentData *X = fragments.fragment(x); + int oldsize = X->size_array[0]; + fragments.setSize(x, pos-k); + uint n = fragments.insert_single(pos, oldsize-(pos-k)); + X = fragments.fragment(x); + QTextFragmentData *N = fragments.fragment(n); + N->stringPosition = X->stringPosition + pos-k; + N->format = X->format; + return true; + } + } + return false; +} + +bool QTextDocumentPrivate::unite(uint f) +{ + uint n = fragments.next(f); + if (!n) + return false; + + QTextFragmentData *ff = fragments.fragment(f); + QTextFragmentData *nf = fragments.fragment(n); + + if (nf->format == ff->format && (ff->stringPosition + (int)ff->size_array[0] == nf->stringPosition)) { + if (isValidBlockSeparator(text.at(ff->stringPosition)) + || isValidBlockSeparator(text.at(nf->stringPosition))) + return false; + + fragments.setSize(f, ff->size_array[0] + nf->size_array[0]); + fragments.erase_single(n); + return true; + } + return false; +} + + +int QTextDocumentPrivate::undoRedo(bool undo) +{ + PMDEBUG("%s, undoState=%d, undoStack size=%d", undo ? "undo:" : "redo:", undoState, undoStack.size()); + if (!undoEnabled || (undo && undoState == 0) || (!undo && undoState == undoStack.size())) + return -1; + + undoEnabled = false; + beginEditBlock(); + int editPos = -1; + int editLength = -1; + while (1) { + if (undo) + --undoState; + QTextUndoCommand &c = undoStack[undoState]; + int resetBlockRevision = c.pos; + + switch(c.command) { + case QTextUndoCommand::Inserted: + remove(c.pos, c.length, (QTextUndoCommand::Operation)c.operation); + PMDEBUG(" erase: from %d, length %d", c.pos, c.length); + c.command = QTextUndoCommand::Removed; + editPos = c.pos; + editLength = 0; + break; + case QTextUndoCommand::Removed: + PMDEBUG(" insert: format %d (from %d, length %d, strpos=%d)", c.format, c.pos, c.length, c.strPos); + insert_string(c.pos, c.strPos, c.length, c.format, (QTextUndoCommand::Operation)c.operation); + c.command = QTextUndoCommand::Inserted; + if (editPos != (int)c.pos) + editLength = 0; + editPos = c.pos; + editLength += c.length; + break; + case QTextUndoCommand::BlockInserted: + case QTextUndoCommand::BlockAdded: + remove_block(c.pos, &c.blockFormat, c.command, (QTextUndoCommand::Operation)c.operation); + PMDEBUG(" blockremove: from %d", c.pos); + if (c.command == QTextUndoCommand::BlockInserted) + c.command = QTextUndoCommand::BlockRemoved; + else + c.command = QTextUndoCommand::BlockDeleted; + editPos = c.pos; + editLength = 0; + break; + case QTextUndoCommand::BlockRemoved: + case QTextUndoCommand::BlockDeleted: + PMDEBUG(" blockinsert: charformat %d blockformat %d (pos %d, strpos=%d)", c.format, c.blockFormat, c.pos, c.strPos); + insert_block(c.pos, c.strPos, c.format, c.blockFormat, (QTextUndoCommand::Operation)c.operation, c.command); + resetBlockRevision += 1; + if (c.command == QTextUndoCommand::BlockRemoved) + c.command = QTextUndoCommand::BlockInserted; + else + c.command = QTextUndoCommand::BlockAdded; + if (editPos != (int)c.pos) + editLength = 0; + editPos = c.pos; + editLength += 1; + break; + case QTextUndoCommand::CharFormatChanged: { + resetBlockRevision = -1; // ## TODO + PMDEBUG(" charFormat: format %d (from %d, length %d)", c.format, c.pos, c.length); + FragmentIterator it = find(c.pos); + Q_ASSERT(!it.atEnd()); + + int oldFormat = it.value()->format; + setCharFormat(c.pos, c.length, formats.charFormat(c.format)); + c.format = oldFormat; + if (editPos != (int)c.pos) + editLength = 0; + editPos = c.pos; + editLength += c.length; + break; + } + case QTextUndoCommand::BlockFormatChanged: { + resetBlockRevision = -1; // ## TODO + PMDEBUG(" blockformat: format %d pos %d", c.format, c.pos); + QTextBlock it = blocksFind(c.pos); + Q_ASSERT(it.isValid()); + + int oldFormat = block(it)->format; + block(it)->format = c.format; + QTextBlockGroup *oldGroup = qobject_cast(objectForFormat(formats.blockFormat(oldFormat))); + QTextBlockGroup *group = qobject_cast(objectForFormat(formats.blockFormat(c.format))); + c.format = oldFormat; + if (group != oldGroup) { + if (oldGroup) + oldGroup->blockRemoved(it); + if (group) + group->blockInserted(it); + } else if (group) { + group->blockFormatChanged(it); + } + documentChange(it.position(), it.length()); + editPos = -1; + break; + } + case QTextUndoCommand::GroupFormatChange: { + resetBlockRevision = -1; // ## TODO + PMDEBUG(" group format change"); + QTextObject *object = objectForIndex(c.objectIndex); + int oldFormat = formats.objectFormatIndex(c.objectIndex); + changeObjectFormat(object, c.format); + c.format = oldFormat; + editPos = -1; + break; + } + case QTextUndoCommand::CursorMoved: + editPos = c.pos; + editLength = 0; + break; + case QTextUndoCommand::Custom: + resetBlockRevision = -1; // ## TODO + if (undo) + c.custom->undo(); + else + c.custom->redo(); + editPos = -1; + break; + default: + Q_ASSERT(false); + } + + if (resetBlockRevision >= 0) { + int b = blocks.findNode(resetBlockRevision); + QTextBlockData *B = blocks.fragment(b); + B->revision = c.revision; + } + + if (!undo) + ++undoState; + + bool inBlock = ( + undoState > 0 + && undoState < undoStack.size() + && undoStack[undoState].block_part + && undoStack[undoState-1].block_part + && !undoStack[undoState-1].block_end + ); + if (!inBlock) + break; + } + undoEnabled = true; + + int newCursorPos = -1; + + if (editPos >=0) + newCursorPos = editPos + editLength; + else if (docChangeFrom >= 0) + newCursorPos= qMin(docChangeFrom + docChangeLength, length() - 1); + + endEditBlock(); + emitUndoAvailable(isUndoAvailable()); + emitRedoAvailable(isRedoAvailable()); + + return newCursorPos; +} + +/*! + Appends a custom undo \a item to the undo stack. +*/ +void QTextDocumentPrivate::appendUndoItem(QAbstractUndoItem *item) +{ + if (!undoEnabled) { + delete item; + return; + } + + QTextUndoCommand c; + c.command = QTextUndoCommand::Custom; + c.block_part = editBlock != 0; + c.block_end = 0; + c.operation = QTextUndoCommand::MoveCursor; + c.format = 0; + c.strPos = 0; + c.pos = 0; + c.blockFormat = 0; + + c.custom = item; + appendUndoItem(c); +} + +void QTextDocumentPrivate::appendUndoItem(const QTextUndoCommand &c) +{ + PMDEBUG("appendUndoItem, command=%d enabled=%d", c.command, undoEnabled); + if (!undoEnabled) + return; + if (undoState < undoStack.size()) + clearUndoRedoStacks(QTextDocument::RedoStack); + + if (editBlock != 0 && editBlockCursorPosition >= 0) { // we had a beginEditBlock() with a cursor position + if (c.pos != (quint32) editBlockCursorPosition) { // and that cursor position is different from the command + // generate a CursorMoved undo item + QT_INIT_TEXTUNDOCOMMAND(cc, QTextUndoCommand::CursorMoved, true, QTextUndoCommand::MoveCursor, + 0, 0, editBlockCursorPosition, 0, 0); + undoStack.append(cc); + undoState++; + editBlockCursorPosition = -1; + } + } + + + if (!undoStack.isEmpty() && modified) { + QTextUndoCommand &last = undoStack[undoState - 1]; + + if ( (last.block_part && c.block_part && !last.block_end) // part of the same block => can merge + || (!c.block_part && !last.block_part)) { // two single undo items => can merge + + if (last.tryMerge(c)) + return; + } + } + if (modifiedState > undoState) + modifiedState = -1; + undoStack.append(c); + undoState++; + emitUndoAvailable(true); + emitRedoAvailable(false); + + if (!c.block_part) + emit document()->undoCommandAdded(); +} + +void QTextDocumentPrivate::clearUndoRedoStacks(QTextDocument::Stacks stacksToClear, + bool emitSignals) +{ + bool undoCommandsAvailable = undoState != 0; + bool redoCommandsAvailable = undoState != undoStack.size(); + if (stacksToClear == QTextDocument::UndoStack && undoCommandsAvailable) { + for (int i = 0; i < undoState; ++i) { + QTextUndoCommand c = undoStack[undoState]; + if (c.command & QTextUndoCommand::Custom) + delete c.custom; + } + undoStack.remove(0, undoState); + undoStack.resize(undoStack.size() - undoState); + undoState = 0; + if (emitSignals) + emitUndoAvailable(false); + } else if (stacksToClear == QTextDocument::RedoStack + && redoCommandsAvailable) { + for (int i = undoState; i < undoStack.size(); ++i) { + QTextUndoCommand c = undoStack[i]; + if (c.command & QTextUndoCommand::Custom) + delete c.custom; + } + undoStack.resize(undoState); + if (emitSignals) + emitRedoAvailable(false); + } else if (stacksToClear == QTextDocument::UndoAndRedoStacks + && !undoStack.isEmpty()) { + for (int i = 0; i < undoStack.size(); ++i) { + QTextUndoCommand c = undoStack[i]; + if (c.command & QTextUndoCommand::Custom) + delete c.custom; + } + undoState = 0; + undoStack.resize(0); + if (emitSignals && undoCommandsAvailable) + emitUndoAvailable(false); + if (emitSignals && redoCommandsAvailable) + emitRedoAvailable(false); + } +} + +void QTextDocumentPrivate::emitUndoAvailable(bool available) +{ + if (available != wasUndoAvailable) { + Q_Q(QTextDocument); + emit q->undoAvailable(available); + wasUndoAvailable = available; + } +} + +void QTextDocumentPrivate::emitRedoAvailable(bool available) +{ + if (available != wasRedoAvailable) { + Q_Q(QTextDocument); + emit q->redoAvailable(available); + wasRedoAvailable = available; + } +} + +void QTextDocumentPrivate::enableUndoRedo(bool enable) +{ + if (enable && maximumBlockCount > 0) + return; + + if (!enable) { + undoState = 0; + clearUndoRedoStacks(QTextDocument::RedoStack); + emitUndoAvailable(false); + emitRedoAvailable(false); + } + modifiedState = modified ? -1 : undoState; + undoEnabled = enable; + if (!undoEnabled) + compressPieceTable(); +} + +void QTextDocumentPrivate::joinPreviousEditBlock() +{ + beginEditBlock(); + + if (undoEnabled && undoState) + undoStack[undoState - 1].block_end = false; +} + +void QTextDocumentPrivate::endEditBlock() +{ + Q_ASSERT(editBlock > 0); + if (--editBlock) + return; + + if (undoEnabled && undoState > 0) { + const bool wasBlocking = !undoStack[undoState - 1].block_end; + if (undoStack[undoState - 1].block_part) { + undoStack[undoState - 1].block_end = true; + if (wasBlocking) + emit document()->undoCommandAdded(); + } + } + + editBlockCursorPosition = -1; + + finishEdit(); +} + +void QTextDocumentPrivate::finishEdit() +{ + Q_Q(QTextDocument); + + if (editBlock) + return; + + if (framesDirty) + scan_frames(docChangeFrom, docChangeOldLength, docChangeLength); + + if (lout && docChangeFrom >= 0) { + if (!inContentsChange) { + inContentsChange = true; + emit q->contentsChange(docChangeFrom, docChangeOldLength, docChangeLength); + inContentsChange = false; + } + lout->documentChanged(docChangeFrom, docChangeOldLength, docChangeLength); + } + + docChangeFrom = -1; + + if (needsEnsureMaximumBlockCount) { + needsEnsureMaximumBlockCount = false; + if (ensureMaximumBlockCount()) { + // if ensureMaximumBlockCount() returns true + // it will have called endEditBlock() and + // compressPieceTable() itself, so we return here + // to prevent getting two contentsChanged emits + return; + } + } + + QList changedCursors; + foreach (QTextCursorPrivate *curs, cursors) { + if (curs->changed) { + curs->changed = false; + changedCursors.append(QTextCursor(curs)); + } + } + foreach (const QTextCursor &cursor, changedCursors) + emit q->cursorPositionChanged(cursor); + + contentsChanged(); + + if (blocks.numNodes() != lastBlockCount) { + lastBlockCount = blocks.numNodes(); + emit q->blockCountChanged(lastBlockCount); + } + + if (!undoEnabled && unreachableCharacterCount) + compressPieceTable(); +} + +void QTextDocumentPrivate::documentChange(int from, int length) +{ +// qDebug("QTextDocumentPrivate::documentChange: from=%d,length=%d", from, length); + if (docChangeFrom < 0) { + docChangeFrom = from; + docChangeOldLength = length; + docChangeLength = length; + return; + } + int start = qMin(from, docChangeFrom); + int end = qMax(from + length, docChangeFrom + docChangeLength); + int diff = qMax(0, end - start - docChangeLength); + docChangeFrom = start; + docChangeOldLength += diff; + docChangeLength += diff; +} + +/* + adjustDocumentChangesAndCursors is called whenever there is an insert or remove of characters. + param from is the cursor position in the document + param addedOrRemoved is the amount of characters added or removed. A negative number means characters are removed. + + The function stores information to be emitted when finishEdit() is called. +*/ +void QTextDocumentPrivate::adjustDocumentChangesAndCursors(int from, int addedOrRemoved, QTextUndoCommand::Operation op) +{ + if (!editBlock) + ++revision; + + if (blockCursorAdjustment) { + ; // postpone, will be called again from QTextDocumentPrivate::remove() + } else { + foreach (QTextCursorPrivate *curs, cursors) { + if (curs->adjustPosition(from, addedOrRemoved, op) == QTextCursorPrivate::CursorMoved) { + curs->changed = true; + } + } + } + +// qDebug("QTextDocumentPrivate::adjustDocumentChanges: from=%d,addedOrRemoved=%d", from, addedOrRemoved); + if (docChangeFrom < 0) { + docChangeFrom = from; + if (addedOrRemoved > 0) { + docChangeOldLength = 0; + docChangeLength = addedOrRemoved; + } else { + docChangeOldLength = -addedOrRemoved; + docChangeLength = 0; + } +// qDebug("adjustDocumentChanges:"); +// qDebug(" -> %d %d %d", docChangeFrom, docChangeOldLength, docChangeLength); + return; + } + + // have to merge the new change with the already existing one. + int added = qMax(0, addedOrRemoved); + int removed = qMax(0, -addedOrRemoved); + + int diff = 0; + if(from + removed < docChangeFrom) + diff = docChangeFrom - from - removed; + else if(from > docChangeFrom + docChangeLength) + diff = from - (docChangeFrom + docChangeLength); + + int overlap_start = qMax(from, docChangeFrom); + int overlap_end = qMin(from + removed, docChangeFrom + docChangeLength); + int removedInside = qMax(0, overlap_end - overlap_start); + removed -= removedInside; + +// qDebug("adjustDocumentChanges: from=%d, addedOrRemoved=%d, diff=%d, removedInside=%d", from, addedOrRemoved, diff, removedInside); + docChangeFrom = qMin(docChangeFrom, from); + docChangeOldLength += removed + diff; + docChangeLength += added - removedInside + diff; +// qDebug(" -> %d %d %d", docChangeFrom, docChangeOldLength, docChangeLength); + +} + + +QString QTextDocumentPrivate::plainText() const +{ + QString result; + result.resize(length()); + const QChar *text_unicode = text.unicode(); + QChar *data = result.data(); + for (QTextDocumentPrivate::FragmentIterator it = begin(); it != end(); ++it) { + const QTextFragmentData *f = *it; + ::memcpy(data, text_unicode + f->stringPosition, f->size_array[0] * sizeof(QChar)); + data += f->size_array[0]; + } + // remove trailing block separator + result.chop(1); + return result; +} + +int QTextDocumentPrivate::blockCharFormatIndex(int node) const +{ + int pos = blocks.position(node); + if (pos == 0) + return initialBlockCharFormatIndex; + + return fragments.find(pos - 1)->format; +} + +int QTextDocumentPrivate::nextCursorPosition(int position, QTextLayout::CursorMode mode) const +{ + if (position == length()-1) + return position; + + QTextBlock it = blocksFind(position); + int start = it.position(); + int end = start + it.length() - 1; + if (position == end) + return end + 1; + + return it.layout()->nextCursorPosition(position-start, mode) + start; +} + +int QTextDocumentPrivate::previousCursorPosition(int position, QTextLayout::CursorMode mode) const +{ + if (position == 0) + return position; + + QTextBlock it = blocksFind(position); + int start = it.position(); + if (position == start) + return start - 1; + + return it.layout()->previousCursorPosition(position-start, mode) + start; +} + +void QTextDocumentPrivate::changeObjectFormat(QTextObject *obj, int format) +{ + beginEditBlock(); + int objectIndex = obj->objectIndex(); + int oldFormatIndex = formats.objectFormatIndex(objectIndex); + formats.setObjectFormatIndex(objectIndex, format); + + QTextBlockGroup *b = qobject_cast(obj); + if (b) { + b->d_func()->markBlocksDirty(); + } + QTextFrame *f = qobject_cast(obj); + if (f) + documentChange(f->firstPosition(), f->lastPosition() - f->firstPosition()); + + QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::GroupFormatChange, (editBlock != 0), QTextUndoCommand::MoveCursor, oldFormatIndex, + 0, 0, obj->d_func()->objectIndex, 0); + appendUndoItem(c); + + endEditBlock(); +} + +static QTextFrame *findChildFrame(QTextFrame *f, int pos) +{ + /* Binary search for frame at pos */ + const QList children = f->childFrames(); + int first = 0; + int last = children.size() - 1; + while (first <= last) { + int mid = (first + last) / 2; + QTextFrame *c = children.at(mid); + if (pos > c->lastPosition()) + first = mid + 1; + else if (pos < c->firstPosition()) + last = mid - 1; + else + return c; + } + return 0; +} + +QTextFrame *QTextDocumentPrivate::rootFrame() const +{ + if (!rtFrame) { + QTextFrameFormat defaultRootFrameFormat; + defaultRootFrameFormat.setMargin(documentMargin); + rtFrame = qobject_cast(const_cast(this)->createObject(defaultRootFrameFormat)); + } + return rtFrame; +} + +QTextFrame *QTextDocumentPrivate::frameAt(int pos) const +{ + QTextFrame *f = rootFrame(); + + while (1) { + QTextFrame *c = findChildFrame(f, pos); + if (!c) + return f; + f = c; + } +} + +void QTextDocumentPrivate::clearFrame(QTextFrame *f) +{ + for (int i = 0; i < f->d_func()->childFrames.count(); ++i) + clearFrame(f->d_func()->childFrames.at(i)); + f->d_func()->childFrames.clear(); + f->d_func()->parentFrame = 0; +} + +void QTextDocumentPrivate::scan_frames(int pos, int charsRemoved, int charsAdded) +{ + // ###### optimize + Q_UNUSED(pos); + Q_UNUSED(charsRemoved); + Q_UNUSED(charsAdded); + + QTextFrame *f = rootFrame(); + clearFrame(f); + + for (FragmentIterator it = begin(); it != end(); ++it) { + // QTextFormat fmt = formats.format(it->format); + QTextFrame *frame = qobject_cast(objectForFormat(it->format)); + if (!frame) + continue; + + Q_ASSERT(it.size() == 1); + QChar ch = text.at(it->stringPosition); + + if (ch == QTextBeginningOfFrame) { + if (f != frame) { + // f == frame happens for tables + Q_ASSERT(frame->d_func()->fragment_start == it.n || frame->d_func()->fragment_start == 0); + frame->d_func()->parentFrame = f; + f->d_func()->childFrames.append(frame); + f = frame; + } + } else if (ch == QTextEndOfFrame) { + Q_ASSERT(f == frame); + Q_ASSERT(frame->d_func()->fragment_end == it.n || frame->d_func()->fragment_end == 0); + f = frame->d_func()->parentFrame; + } else if (ch == QChar::ObjectReplacementCharacter) { + Q_ASSERT(f != frame); + Q_ASSERT(frame->d_func()->fragment_start == it.n || frame->d_func()->fragment_start == 0); + Q_ASSERT(frame->d_func()->fragment_end == it.n || frame->d_func()->fragment_end == 0); + frame->d_func()->parentFrame = f; + f->d_func()->childFrames.append(frame); + } else { + Q_ASSERT(false); + } + } + Q_ASSERT(f == rtFrame); + framesDirty = false; +} + +void QTextDocumentPrivate::insert_frame(QTextFrame *f) +{ + int start = f->firstPosition(); + int end = f->lastPosition(); + QTextFrame *parent = frameAt(start-1); + Q_ASSERT(parent == frameAt(end+1)); + + if (start != end) { + // iterator over the parent and move all children contained in my frame to myself + for (int i = 0; i < parent->d_func()->childFrames.size(); ++i) { + QTextFrame *c = parent->d_func()->childFrames.at(i); + if (start < c->firstPosition() && end > c->lastPosition()) { + parent->d_func()->childFrames.removeAt(i); + f->d_func()->childFrames.append(c); + c->d_func()->parentFrame = f; + } + } + } + // insert at the correct position + int i = 0; + for (; i < parent->d_func()->childFrames.size(); ++i) { + QTextFrame *c = parent->d_func()->childFrames.at(i); + if (c->firstPosition() > end) + break; + } + parent->d_func()->childFrames.insert(i, f); + f->d_func()->parentFrame = parent; +} + +QTextFrame *QTextDocumentPrivate::insertFrame(int start, int end, const QTextFrameFormat &format) +{ + Q_ASSERT(start >= 0 && start < length()); + Q_ASSERT(end >= 0 && end < length()); + Q_ASSERT(start <= end || end == -1); + + if (start != end && frameAt(start) != frameAt(end)) + return 0; + + beginEditBlock(); + + QTextFrame *frame = qobject_cast(createObject(format)); + Q_ASSERT(frame); + + // #### using the default block and char format below might be wrong + int idx = formats.indexForFormat(QTextBlockFormat()); + QTextCharFormat cfmt; + cfmt.setObjectIndex(frame->objectIndex()); + int charIdx = formats.indexForFormat(cfmt); + + insertBlock(QTextBeginningOfFrame, start, idx, charIdx, QTextUndoCommand::MoveCursor); + insertBlock(QTextEndOfFrame, ++end, idx, charIdx, QTextUndoCommand::KeepCursor); + + frame->d_func()->fragment_start = find(start).n; + frame->d_func()->fragment_end = find(end).n; + + insert_frame(frame); + + endEditBlock(); + + return frame; +} + +void QTextDocumentPrivate::removeFrame(QTextFrame *frame) +{ + QTextFrame *parent = frame->d_func()->parentFrame; + if (!parent) + return; + + int start = frame->firstPosition(); + int end = frame->lastPosition(); + Q_ASSERT(end >= start); + + beginEditBlock(); + + // remove already removes the frames from the tree + remove(end, 1); + remove(start-1, 1); + + endEditBlock(); +} + +QTextObject *QTextDocumentPrivate::objectForIndex(int objectIndex) const +{ + if (objectIndex < 0) + return 0; + + QTextObject *object = objects.value(objectIndex, 0); + if (!object) { + QTextDocumentPrivate *that = const_cast(this); + QTextFormat fmt = formats.objectFormat(objectIndex); + object = that->createObject(fmt, objectIndex); + } + return object; +} + +QTextObject *QTextDocumentPrivate::objectForFormat(int formatIndex) const +{ + int objectIndex = formats.format(formatIndex).objectIndex(); + return objectForIndex(objectIndex); +} + +QTextObject *QTextDocumentPrivate::objectForFormat(const QTextFormat &f) const +{ + return objectForIndex(f.objectIndex()); +} + +QTextObject *QTextDocumentPrivate::createObject(const QTextFormat &f, int objectIndex) +{ + QTextObject *obj = document()->createObject(f); + + if (obj) { + obj->d_func()->objectIndex = objectIndex == -1 ? formats.createObjectIndex(f) : objectIndex; + objects[obj->d_func()->objectIndex] = obj; + } + + return obj; +} + +void QTextDocumentPrivate::deleteObject(QTextObject *object) +{ + const int objIdx = object->d_func()->objectIndex; + objects.remove(objIdx); + delete object; +} + +void QTextDocumentPrivate::contentsChanged() +{ + Q_Q(QTextDocument); + if (editBlock) + return; + + bool m = undoEnabled ? (modifiedState != undoState) : true; + if (modified != m) { + modified = m; + emit q->modificationChanged(modified); + } + + emit q->contentsChanged(); +} + +void QTextDocumentPrivate::compressPieceTable() +{ + if (undoEnabled) + return; + + const uint garbageCollectionThreshold = 96 * 1024; // bytes + + //qDebug() << "unreachable bytes:" << unreachableCharacterCount * sizeof(QChar) << " -- limit" << garbageCollectionThreshold << "text size =" << text.size() << "capacity:" << text.capacity(); + + bool compressTable = unreachableCharacterCount * sizeof(QChar) > garbageCollectionThreshold + && text.size() >= text.capacity() * 0.9; + if (!compressTable) + return; + + QString newText; + newText.resize(text.size()); + QChar *newTextPtr = newText.data(); + int newLen = 0; + + for (FragmentMap::Iterator it = fragments.begin(); !it.atEnd(); ++it) { + memcpy(newTextPtr, text.constData() + it->stringPosition, it->size_array[0] * sizeof(QChar)); + it->stringPosition = newLen; + newTextPtr += it->size_array[0]; + newLen += it->size_array[0]; + } + + newText.resize(newLen); + newText.squeeze(); + //qDebug() << "removed" << text.size() - newText.size() << "characters"; + text = newText; + unreachableCharacterCount = 0; +} + +void QTextDocumentPrivate::setModified(bool m) +{ + Q_Q(QTextDocument); + if (m == modified) + return; + + modified = m; + if (!modified) + modifiedState = undoState; + else + modifiedState = -1; + + emit q->modificationChanged(modified); +} + +bool QTextDocumentPrivate::ensureMaximumBlockCount() +{ + if (maximumBlockCount <= 0) + return false; + if (blocks.numNodes() <= maximumBlockCount) + return false; + + beginEditBlock(); + + const int blocksToRemove = blocks.numNodes() - maximumBlockCount; + QTextCursor cursor(this, 0); + cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor, blocksToRemove); + + unreachableCharacterCount += cursor.selectionEnd() - cursor.selectionStart(); + + // preserve the char format of the paragraph that is to become the new first one + QTextCharFormat charFmt = cursor.blockCharFormat(); + cursor.removeSelectedText(); + cursor.setBlockCharFormat(charFmt); + + endEditBlock(); + + compressPieceTable(); + + return true; +} + +/// This method is called from QTextTable when it is about to remove a table-cell to allow cursors to update their selection. +void QTextDocumentPrivate::aboutToRemoveCell(int from, int to) +{ + Q_ASSERT(from <= to); + foreach (QTextCursorPrivate *curs, cursors) + curs->aboutToRemoveCell(from, to); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextdocument_p.h b/src/gui/text/qtextdocument_p.h new file mode 100644 index 0000000000..b464f2ee40 --- /dev/null +++ b/src/gui/text/qtextdocument_p.h @@ -0,0 +1,408 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTDOCUMENT_P_H +#define QTEXTDOCUMENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qglobal.h" +#include "QtCore/qstring.h" +#include "QtCore/qvector.h" +#include "QtCore/qlist.h" +#include "private/qobject_p.h" +#include "private/qfragmentmap_p.h" +#include "QtGui/qtextlayout.h" +#include "QtGui/qtextoption.h" +#include "private/qtextformat_p.h" +#include "QtGui/qtextdocument.h" +#include "QtGui/qtextobject.h" +#include "QtCore/qmap.h" +#include "QtCore/qvariant.h" +#include "QtCore/qurl.h" +#include "private/qcssparser_p.h" + +// #define QT_QMAP_DEBUG + +#ifdef QT_QMAP_DEBUG +#include +#endif + +QT_BEGIN_NAMESPACE + +class QTextFormatCollection; +class QTextFormat; +class QTextBlockFormat; +class QTextCursorPrivate; +class QAbstractTextDocumentLayout; +class QTextDocument; +class QTextFrame; + +#define QTextBeginningOfFrame QChar(0xfdd0) +#define QTextEndOfFrame QChar(0xfdd1) + +class QTextFragmentData : public QFragment<> +{ +public: + inline void initialize() {} + inline void invalidate() const {} + inline void free() {} + int stringPosition; + int format; +}; + +class QTextBlockData : public QFragment<3> +{ +public: + inline void initialize() + { layout = 0; userData = 0; userState = -1; revision = 0; hidden = 0; } + void invalidate() const; + inline void free() + { delete layout; layout = 0; delete userData; userData = 0; } + + mutable int format; + // ##### probably store a QTextEngine * here! + mutable QTextLayout *layout; + mutable QTextBlockUserData *userData; + mutable int userState; + mutable int revision : 31; + mutable uint hidden : 1; +}; + + +class QAbstractUndoItem; + +class QTextUndoCommand +{ +public: + enum Command { + Inserted = 0, + Removed = 1, + CharFormatChanged = 2, + BlockFormatChanged = 3, + BlockInserted = 4, + BlockRemoved = 5, + BlockAdded = 6, + BlockDeleted = 7, + GroupFormatChange = 8, + CursorMoved = 9, + Custom = 256 + }; + enum Operation { + KeepCursor = 0, + MoveCursor = 1 + }; + quint16 command; + uint block_part : 1; // all commands that are part of an undo block (including the first and the last one) have this set to 1 + uint block_end : 1; // the last command in an undo block has this set to 1. + uint block_padding : 6; // padding since block used to be a quint8 + quint8 operation; + int format; + quint32 strPos; + quint32 pos; + union { + int blockFormat; + quint32 length; + QAbstractUndoItem *custom; + int objectIndex; + }; + quint32 revision; + + bool tryMerge(const QTextUndoCommand &other); +}; +Q_DECLARE_TYPEINFO(QTextUndoCommand, Q_PRIMITIVE_TYPE); + +class Q_AUTOTEST_EXPORT QTextDocumentPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QTextDocument) +public: + typedef QFragmentMap FragmentMap; + typedef FragmentMap::ConstIterator FragmentIterator; + typedef QFragmentMap BlockMap; + + QTextDocumentPrivate(); + ~QTextDocumentPrivate(); + + void init(); + void clear(); + + void setLayout(QAbstractTextDocumentLayout *layout); + + void insert(int pos, const QString &text, int format); + void insert(int pos, int strPos, int strLength, int format); + int insertBlock(int pos, int blockFormat, int charFormat, QTextUndoCommand::Operation = QTextUndoCommand::MoveCursor); + int insertBlock(const QChar &blockSeparator, int pos, int blockFormat, int charFormat, + QTextUndoCommand::Operation op = QTextUndoCommand::MoveCursor); + + void move(int from, int to, int length, QTextUndoCommand::Operation = QTextUndoCommand::MoveCursor); + void remove(int pos, int length, QTextUndoCommand::Operation = QTextUndoCommand::MoveCursor); + + void aboutToRemoveCell(int cursorFrom, int cursorEnd); + + QTextFrame *insertFrame(int start, int end, const QTextFrameFormat &format); + void removeFrame(QTextFrame *frame); + + enum FormatChangeMode { MergeFormat, SetFormat, SetFormatAndPreserveObjectIndices }; + + void setCharFormat(int pos, int length, const QTextCharFormat &newFormat, FormatChangeMode mode = SetFormat); + void setBlockFormat(const QTextBlock &from, const QTextBlock &to, + const QTextBlockFormat &newFormat, FormatChangeMode mode = SetFormat); + + void emitUndoAvailable(bool available); + void emitRedoAvailable(bool available); + + int undoRedo(bool undo); + inline void undo() { undoRedo(true); } + inline void redo() { undoRedo(false); } + void appendUndoItem(QAbstractUndoItem *); + inline void beginEditBlock() { if (0 == editBlock++) ++revision; } + void joinPreviousEditBlock(); + void endEditBlock(); + void finishEdit(); + inline bool isInEditBlock() const { return editBlock; } + void enableUndoRedo(bool enable); + inline bool isUndoRedoEnabled() const { return undoEnabled; } + + inline bool isUndoAvailable() const { return undoEnabled && undoState > 0; } + inline bool isRedoAvailable() const { return undoEnabled && undoState < undoStack.size(); } + + inline int availableUndoSteps() const { return undoEnabled ? undoState : 0; } + inline int availableRedoSteps() const { return undoEnabled ? qMax(undoStack.size() - undoState - 1, 0) : 0; } + + inline QString buffer() const { return text; } + QString plainText() const; + inline int length() const { return fragments.length(); } + + inline QTextFormatCollection *formatCollection() { return &formats; } + inline const QTextFormatCollection *formatCollection() const { return &formats; } + inline QAbstractTextDocumentLayout *layout() const { return lout; } + + inline FragmentIterator find(int pos) const { return fragments.find(pos); } + inline FragmentIterator begin() const { return fragments.begin(); } + inline FragmentIterator end() const { return fragments.end(); } + + inline QTextBlock blocksBegin() const { return QTextBlock(const_cast(this), blocks.firstNode()); } + inline QTextBlock blocksEnd() const { return QTextBlock(const_cast(this), 0); } + inline QTextBlock blocksFind(int pos) const { return QTextBlock(const_cast(this), blocks.findNode(pos)); } + int blockCharFormatIndex(int node) const; + + inline int numBlocks() const { return blocks.numNodes(); } + + const BlockMap &blockMap() const { return blocks; } + const FragmentMap &fragmentMap() const { return fragments; } + BlockMap &blockMap() { return blocks; } + FragmentMap &fragmentMap() { return fragments; } + + static const QTextBlockData *block(const QTextBlock &it) { return it.p->blocks.fragment(it.n); } + + int nextCursorPosition(int position, QTextLayout::CursorMode mode) const; + int previousCursorPosition(int position, QTextLayout::CursorMode mode) const; + + void changeObjectFormat(QTextObject *group, int format); + + void setModified(bool m); + inline bool isModified() const { return modified; } + + inline QFont defaultFont() const { return formats.defaultFont(); } + inline void setDefaultFont(const QFont &f) { formats.setDefaultFont(f); } + + void clearUndoRedoStacks(QTextDocument::Stacks stacksToClear, bool emitSignals = false); + +private: + bool split(int pos); + bool unite(uint f); + + void insert_string(int pos, uint strPos, uint length, int format, QTextUndoCommand::Operation op); + int insert_block(int pos, uint strPos, int format, int blockformat, QTextUndoCommand::Operation op, int command); + int remove_string(int pos, uint length, QTextUndoCommand::Operation op); + int remove_block(int pos, int *blockformat, int command, QTextUndoCommand::Operation op); + + void insert_frame(QTextFrame *f); + void scan_frames(int pos, int charsRemoved, int charsAdded); + static void clearFrame(QTextFrame *f); + + void adjustDocumentChangesAndCursors(int from, int addedOrRemoved, QTextUndoCommand::Operation op); + + bool wasUndoAvailable; + bool wasRedoAvailable; + +public: + void documentChange(int from, int length); + + inline void addCursor(QTextCursorPrivate *c) { cursors.append(c); } + inline void removeCursor(QTextCursorPrivate *c) { cursors.removeAll(c); } + + QTextFrame *frameAt(int pos) const; + QTextFrame *rootFrame() const; + + QTextObject *objectForIndex(int objectIndex) const; + QTextObject *objectForFormat(int formatIndex) const; + QTextObject *objectForFormat(const QTextFormat &f) const; + + QTextObject *createObject(const QTextFormat &newFormat, int objectIndex = -1); + void deleteObject(QTextObject *object); + + QTextDocument *document() { return q_func(); } + const QTextDocument *document() const { return q_func(); } + + bool ensureMaximumBlockCount(); + +private: + QTextDocumentPrivate(const QTextDocumentPrivate& m); + QTextDocumentPrivate& operator= (const QTextDocumentPrivate& m); + + void appendUndoItem(const QTextUndoCommand &c); + + void contentsChanged(); + + void compressPieceTable(); + + QString text; + uint unreachableCharacterCount; + + QVector undoStack; + bool undoEnabled; + int undoState; + int revision; + // position in undo stack of the last setModified(false) call + int modifiedState; + bool modified; + + int editBlock; + int editBlockCursorPosition; + int docChangeFrom; + int docChangeOldLength; + int docChangeLength; + bool framesDirty; + + QTextFormatCollection formats; + mutable QTextFrame *rtFrame; + QAbstractTextDocumentLayout *lout; + FragmentMap fragments; + BlockMap blocks; + int initialBlockCharFormatIndex; + + QList cursors; + QMap objects; + QMap resources; + QMap cachedResources; + QString defaultStyleSheet; + + int lastBlockCount; + +public: + QTextOption defaultTextOption; +#ifndef QT_NO_CSSPARSER + QCss::StyleSheet parsedDefaultStyleSheet; +#endif + int maximumBlockCount; + uint needsEnsureMaximumBlockCount : 1; + uint inContentsChange : 1; + uint blockCursorAdjustment : 1; + QSizeF pageSize; + QString title; + QString url; + qreal indentWidth; + qreal documentMargin; + + void mergeCachedResources(const QTextDocumentPrivate *priv); + + friend class QTextHtmlExporter; + friend class QTextCursor; +}; + +class QTextTable; +class QTextHtmlExporter +{ +public: + QTextHtmlExporter(const QTextDocument *_doc); + + enum ExportMode { + ExportEntireDocument, + ExportFragment + }; + + QString toHtml(const QByteArray &encoding, ExportMode mode = ExportEntireDocument); + +private: + enum StyleMode { EmitStyleTag, OmitStyleTag }; + enum FrameType { TextFrame, TableFrame, RootFrame }; + + void emitFrame(QTextFrame::Iterator frameIt); + void emitTextFrame(const QTextFrame *frame); + void emitBlock(const QTextBlock &block); + void emitTable(const QTextTable *table); + void emitFragment(const QTextFragment &fragment); + + void emitBlockAttributes(const QTextBlock &block); + bool emitCharFormatStyle(const QTextCharFormat &format); + void emitTextLength(const char *attribute, const QTextLength &length); + void emitAlignment(Qt::Alignment alignment); + void emitFloatStyle(QTextFrameFormat::Position pos, StyleMode mode = EmitStyleTag); + void emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right); + void emitAttribute(const char *attribute, const QString &value); + void emitFrameStyle(const QTextFrameFormat &format, FrameType frameType); + void emitBorderStyle(QTextFrameFormat::BorderStyle style); + void emitPageBreakPolicy(QTextFormat::PageBreakFlags policy); + + void emitFontFamily(const QString &family); + + void emitBackgroundAttribute(const QTextFormat &format); + QString findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap); + + QString html; + QTextCharFormat defaultCharFormat; + const QTextDocument *doc; + bool fragmentMarkers; +}; + +QT_END_NAMESPACE + +#endif // QTEXTDOCUMENT_P_H diff --git a/src/gui/text/qtextdocumentfragment.cpp b/src/gui/text/qtextdocumentfragment.cpp new file mode 100644 index 0000000000..7890b38067 --- /dev/null +++ b/src/gui/text/qtextdocumentfragment.cpp @@ -0,0 +1,1224 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextdocumentfragment.h" +#include "qtextdocumentfragment_p.h" +#include "qtextcursor_p.h" +#include "qtextlist.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QTextCopyHelper::QTextCopyHelper(const QTextCursor &_source, const QTextCursor &_destination, bool forceCharFormat, const QTextCharFormat &fmt) +#if defined(Q_CC_DIAB) // compiler bug + : formatCollection(*_destination.d->priv->formatCollection()), originalText((const QString)_source.d->priv->buffer()) +#else + : formatCollection(*_destination.d->priv->formatCollection()), originalText(_source.d->priv->buffer()) +#endif +{ + src = _source.d->priv; + dst = _destination.d->priv; + insertPos = _destination.position(); + this->forceCharFormat = forceCharFormat; + primaryCharFormatIndex = convertFormatIndex(fmt); + cursor = _source; +} + +int QTextCopyHelper::convertFormatIndex(const QTextFormat &oldFormat, int objectIndexToSet) +{ + QTextFormat fmt = oldFormat; + if (objectIndexToSet != -1) { + fmt.setObjectIndex(objectIndexToSet); + } else if (fmt.objectIndex() != -1) { + int newObjectIndex = objectIndexMap.value(fmt.objectIndex(), -1); + if (newObjectIndex == -1) { + QTextFormat objFormat = src->formatCollection()->objectFormat(fmt.objectIndex()); + Q_ASSERT(objFormat.objectIndex() == -1); + newObjectIndex = formatCollection.createObjectIndex(objFormat); + objectIndexMap.insert(fmt.objectIndex(), newObjectIndex); + } + fmt.setObjectIndex(newObjectIndex); + } + int idx = formatCollection.indexForFormat(fmt); + Q_ASSERT(formatCollection.format(idx).type() == oldFormat.type()); + return idx; +} + +int QTextCopyHelper::appendFragment(int pos, int endPos, int objectIndex) +{ + QTextDocumentPrivate::FragmentIterator fragIt = src->find(pos); + const QTextFragmentData * const frag = fragIt.value(); + + Q_ASSERT(objectIndex == -1 + || (frag->size_array[0] == 1 && src->formatCollection()->format(frag->format).objectIndex() != -1)); + + int charFormatIndex; + if (forceCharFormat) + charFormatIndex = primaryCharFormatIndex; + else + charFormatIndex = convertFormatIndex(frag->format, objectIndex); + + const int inFragmentOffset = qMax(0, pos - fragIt.position()); + int charsToCopy = qMin(int(frag->size_array[0] - inFragmentOffset), endPos - pos); + + QTextBlock nextBlock = src->blocksFind(pos + 1); + + int blockIdx = -2; + if (nextBlock.position() == pos + 1) { + blockIdx = convertFormatIndex(nextBlock.blockFormat()); + } else if (pos == 0 && insertPos == 0) { + dst->setBlockFormat(dst->blocksBegin(), dst->blocksBegin(), convertFormat(src->blocksBegin().blockFormat()).toBlockFormat()); + dst->setCharFormat(-1, 1, convertFormat(src->blocksBegin().charFormat()).toCharFormat()); + } + + QString txtToInsert(originalText.constData() + frag->stringPosition + inFragmentOffset, charsToCopy); + if (txtToInsert.length() == 1 + && (txtToInsert.at(0) == QChar::ParagraphSeparator + || txtToInsert.at(0) == QTextBeginningOfFrame + || txtToInsert.at(0) == QTextEndOfFrame + ) + ) { + dst->insertBlock(txtToInsert.at(0), insertPos, blockIdx, charFormatIndex); + ++insertPos; + } else { + if (nextBlock.textList()) { + QTextBlock dstBlock = dst->blocksFind(insertPos); + if (!dstBlock.textList()) { + // insert a new text block with the block and char format from the + // source block to make sure that the following text fragments + // end up in a list as they should + int listBlockFormatIndex = convertFormatIndex(nextBlock.blockFormat()); + int listCharFormatIndex = convertFormatIndex(nextBlock.charFormat()); + dst->insertBlock(insertPos, listBlockFormatIndex, listCharFormatIndex); + ++insertPos; + } + } + dst->insert(insertPos, txtToInsert, charFormatIndex); + const int userState = nextBlock.userState(); + if (userState != -1) + dst->blocksFind(insertPos).setUserState(userState); + insertPos += txtToInsert.length(); + } + + return charsToCopy; +} + +void QTextCopyHelper::appendFragments(int pos, int endPos) +{ + Q_ASSERT(pos < endPos); + + while (pos < endPos) + pos += appendFragment(pos, endPos); +} + +void QTextCopyHelper::copy() +{ + if (cursor.hasComplexSelection()) { + QTextTable *table = cursor.currentTable(); + int row_start, col_start, num_rows, num_cols; + cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols); + + QTextTableFormat tableFormat = table->format(); + tableFormat.setColumns(num_cols); + tableFormat.clearColumnWidthConstraints(); + const int objectIndex = dst->formatCollection()->createObjectIndex(tableFormat); + + Q_ASSERT(row_start != -1); + for (int r = row_start; r < row_start + num_rows; ++r) { + for (int c = col_start; c < col_start + num_cols; ++c) { + QTextTableCell cell = table->cellAt(r, c); + const int rspan = cell.rowSpan(); + const int cspan = cell.columnSpan(); + if (rspan != 1) { + int cr = cell.row(); + if (cr != r) + continue; + } + if (cspan != 1) { + int cc = cell.column(); + if (cc != c) + continue; + } + + // add the QTextBeginningOfFrame + QTextCharFormat cellFormat = cell.format(); + if (r + rspan >= row_start + num_rows) { + cellFormat.setTableCellRowSpan(row_start + num_rows - r); + } + if (c + cspan >= col_start + num_cols) { + cellFormat.setTableCellColumnSpan(col_start + num_cols - c); + } + const int charFormatIndex = convertFormatIndex(cellFormat, objectIndex); + + int blockIdx = -2; + const int cellPos = cell.firstPosition(); + QTextBlock block = src->blocksFind(cellPos); + if (block.position() == cellPos) { + blockIdx = convertFormatIndex(block.blockFormat()); + } + + dst->insertBlock(QTextBeginningOfFrame, insertPos, blockIdx, charFormatIndex); + ++insertPos; + + // nothing to add for empty cells + if (cell.lastPosition() > cellPos) { + // add the contents + appendFragments(cellPos, cell.lastPosition()); + } + } + } + + // add end of table + int end = table->lastPosition(); + appendFragment(end, end+1, objectIndex); + } else { + appendFragments(cursor.selectionStart(), cursor.selectionEnd()); + } +} + +QTextDocumentFragmentPrivate::QTextDocumentFragmentPrivate(const QTextCursor &_cursor) + : ref(1), doc(new QTextDocument), importedFromPlainText(false) +{ + doc->setUndoRedoEnabled(false); + + if (!_cursor.hasSelection()) + return; + + doc->docHandle()->beginEditBlock(); + QTextCursor destCursor(doc); + QTextCopyHelper(_cursor, destCursor).copy(); + doc->docHandle()->endEditBlock(); + + if (_cursor.d) + doc->docHandle()->mergeCachedResources(_cursor.d->priv); +} + +void QTextDocumentFragmentPrivate::insert(QTextCursor &_cursor) const +{ + if (_cursor.isNull()) + return; + + QTextDocumentPrivate *destPieceTable = _cursor.d->priv; + destPieceTable->beginEditBlock(); + + QTextCursor sourceCursor(doc); + sourceCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + QTextCopyHelper(sourceCursor, _cursor, importedFromPlainText, _cursor.charFormat()).copy(); + + destPieceTable->endEditBlock(); +} + +/*! + \class QTextDocumentFragment + \reentrant + + \brief The QTextDocumentFragment class represents a piece of formatted text + from a QTextDocument. + + \ingroup richtext-processing + \ingroup shared + + A QTextDocumentFragment is a fragment of rich text, that can be inserted into + a QTextDocument. A document fragment can be created from a + QTextDocument, from a QTextCursor's selection, or from another + document fragment. Document fragments can also be created by the + static functions, fromPlainText() and fromHtml(). + + The contents of a document fragment can be obtained as plain text + by using the toPlainText() function, or it can be obtained as HTML + with toHtml(). +*/ + + +/*! + Constructs an empty QTextDocumentFragment. + + \sa isEmpty() +*/ +QTextDocumentFragment::QTextDocumentFragment() + : d(0) +{ +} + +/*! + Converts the given \a document into a QTextDocumentFragment. + Note that the QTextDocumentFragment only stores the document contents, not meta information + like the document's title. +*/ +QTextDocumentFragment::QTextDocumentFragment(const QTextDocument *document) + : d(0) +{ + if (!document) + return; + + QTextCursor cursor(const_cast(document)); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + d = new QTextDocumentFragmentPrivate(cursor); +} + +/*! + Creates a QTextDocumentFragment from the \a{cursor}'s selection. + If the cursor doesn't have a selection, the created fragment is empty. + + \sa isEmpty() QTextCursor::selection() +*/ +QTextDocumentFragment::QTextDocumentFragment(const QTextCursor &cursor) + : d(0) +{ + if (!cursor.hasSelection()) + return; + + d = new QTextDocumentFragmentPrivate(cursor); +} + +/*! + \fn QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &other) + + Copy constructor. Creates a copy of the \a other fragment. +*/ +QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &rhs) + : d(rhs.d) +{ + if (d) + d->ref.ref(); +} + +/*! + \fn QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &other) + + Assigns the \a other fragment to this fragment. +*/ +QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &rhs) +{ + if (rhs.d) + rhs.d->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = rhs.d; + return *this; +} + +/*! + Destroys the document fragment. +*/ +QTextDocumentFragment::~QTextDocumentFragment() +{ + if (d && !d->ref.deref()) + delete d; +} + +/*! + Returns true if the fragment is empty; otherwise returns false. +*/ +bool QTextDocumentFragment::isEmpty() const +{ + return !d || !d->doc || d->doc->docHandle()->length() <= 1; +} + +/*! + Returns the document fragment's text as plain text (i.e. with no + formatting information). + + \sa toHtml() +*/ +QString QTextDocumentFragment::toPlainText() const +{ + if (!d) + return QString(); + + return d->doc->toPlainText(); +} + +// #### Qt 5: merge with other overload +/*! + \overload +*/ + +#ifndef QT_NO_TEXTHTMLPARSER + +QString QTextDocumentFragment::toHtml() const +{ + return toHtml(QByteArray()); +} + +/*! + \since 4.2 + + Returns the contents of the document fragment as HTML, + using the specified \a encoding (e.g., "UTF-8", "ISO 8859-1"). + + \sa toPlainText(), QTextDocument::toHtml(), QTextCodec +*/ +QString QTextDocumentFragment::toHtml(const QByteArray &encoding) const +{ + if (!d) + return QString(); + + return QTextHtmlExporter(d->doc).toHtml(encoding, QTextHtmlExporter::ExportFragment); +} + +#endif // QT_NO_TEXTHTMLPARSER + +/*! + Returns a document fragment that contains the given \a plainText. + + When inserting such a fragment into a QTextDocument the current char format of + the QTextCursor used for insertion is used as format for the text. +*/ +QTextDocumentFragment QTextDocumentFragment::fromPlainText(const QString &plainText) +{ + QTextDocumentFragment res; + + res.d = new QTextDocumentFragmentPrivate; + res.d->importedFromPlainText = true; + QTextCursor cursor(res.d->doc); + cursor.insertText(plainText); + return res; +} + +static QTextListFormat::Style nextListStyle(QTextListFormat::Style style) +{ + if (style == QTextListFormat::ListDisc) + return QTextListFormat::ListCircle; + else if (style == QTextListFormat::ListCircle) + return QTextListFormat::ListSquare; + return style; +} + +#ifndef QT_NO_TEXTHTMLPARSER + +QTextHtmlImporter::QTextHtmlImporter(QTextDocument *_doc, const QString &_html, ImportMode mode, const QTextDocument *resourceProvider) + : indent(0), compressNextWhitespace(PreserveWhiteSpace), doc(_doc), importMode(mode) +{ + cursor = QTextCursor(doc); + wsm = QTextHtmlParserNode::WhiteSpaceNormal; + + QString html = _html; + const int startFragmentPos = html.indexOf(QLatin1String("")); + if (startFragmentPos != -1) { + QString qt3RichTextHeader(QLatin1String("")); + + // Hack for Qt3 + const bool hasQtRichtextMetaTag = html.contains(qt3RichTextHeader); + + const int endFragmentPos = html.indexOf(QLatin1String("")); + if (startFragmentPos < endFragmentPos) + html = html.mid(startFragmentPos, endFragmentPos - startFragmentPos); + else + html = html.mid(startFragmentPos); + + if (hasQtRichtextMetaTag) + html.prepend(qt3RichTextHeader); + } + + parse(html, resourceProvider ? resourceProvider : doc); +// dumpHtml(); +} + +void QTextHtmlImporter::import() +{ + cursor.beginEditBlock(); + hasBlock = true; + forceBlockMerging = false; + compressNextWhitespace = RemoveWhiteSpace; + blockTagClosed = false; + for (currentNodeIdx = 0; currentNodeIdx < count(); ++currentNodeIdx) { + currentNode = &at(currentNodeIdx); + wsm = textEditMode ? QTextHtmlParserNode::WhiteSpacePreWrap : currentNode->wsm; + + /* + * process each node in three stages: + * 1) check if the hierarchy changed and we therefore passed the + * equivalent of a closing tag -> we may need to finish off + * some structures like tables + * + * 2) check if the current node is a special node like a + * ,
    or tag that requires special processing + * + * 3) if the node should result in a QTextBlock create one and + * finally insert text that may be attached to the node + */ + + /* emit 'closing' table blocks or adjust current indent level + * if we + * 1) are beyond the first node + * 2) the current node not being a child of the previous node + * means there was a tag closing in the input html + */ + if (currentNodeIdx > 0 && (currentNode->parent != currentNodeIdx - 1)) { + blockTagClosed = closeTag(); + // visually collapse subsequent block tags, but if the element after the closed block tag + // is for example an inline element (!isBlock) we have to make sure we start a new paragraph by setting + // hasBlock to false. + if (blockTagClosed + && !currentNode->isBlock() + && currentNode->id != Html_unknown) + { + hasBlock = false; + } else if (hasBlock) { + // when collapsing subsequent block tags we need to clear the block format + QTextBlockFormat blockFormat = currentNode->blockFormat; + blockFormat.setIndent(indent); + + QTextBlockFormat oldFormat = cursor.blockFormat(); + if (oldFormat.hasProperty(QTextFormat::PageBreakPolicy)) { + QTextFormat::PageBreakFlags pageBreak = oldFormat.pageBreakPolicy(); + if (pageBreak == QTextFormat::PageBreak_AlwaysAfter) + /* We remove an empty paragrah that requested a page break after. + moving that request to the next paragraph means we also need to make + that a pagebreak before to keep the same visual appearance. + */ + pageBreak = QTextFormat::PageBreak_AlwaysBefore; + blockFormat.setPageBreakPolicy(pageBreak); + } + + cursor.setBlockFormat(blockFormat); + } + } + + if (currentNode->displayMode == QTextHtmlElement::DisplayNone) { + if (currentNode->id == Html_title) + doc->setMetaInformation(QTextDocument::DocumentTitle, currentNode->text); + // ignore explicitly 'invisible' elements + continue; + } + + if (processSpecialNodes() == ContinueWithNextNode) + continue; + + // make sure there's a block for 'Blah' after
    • foo
    Blah + if (blockTagClosed + && !hasBlock + && !currentNode->isBlock() + && !currentNode->text.isEmpty() && !currentNode->hasOnlyWhitespace() + && currentNode->displayMode == QTextHtmlElement::DisplayInline) { + + QTextBlockFormat block = currentNode->blockFormat; + block.setIndent(indent); + + appendBlock(block, currentNode->charFormat); + + hasBlock = true; + } + + if (currentNode->isBlock()) { + if (processBlockNode() == ContinueWithNextNode) + continue; + } + + if (currentNode->charFormat.isAnchor() && !currentNode->charFormat.anchorName().isEmpty()) { + namedAnchors.append(currentNode->charFormat.anchorName()); + } + + if (appendNodeText()) + hasBlock = false; // if we actually appended text then we don't + // have an empty block anymore + } + + cursor.endEditBlock(); +} + +bool QTextHtmlImporter::appendNodeText() +{ + const int initialCursorPosition = cursor.position(); + QTextCharFormat format = currentNode->charFormat; + + if(wsm == QTextHtmlParserNode::WhiteSpacePre || wsm == QTextHtmlParserNode::WhiteSpacePreWrap) + compressNextWhitespace = PreserveWhiteSpace; + + QString text = currentNode->text; + + QString textToInsert; + textToInsert.reserve(text.size()); + + for (int i = 0; i < text.length(); ++i) { + QChar ch = text.at(i); + + if (ch.isSpace() + && ch != QChar::Nbsp + && ch != QChar::ParagraphSeparator) { + + if (compressNextWhitespace == CollapseWhiteSpace) + compressNextWhitespace = RemoveWhiteSpace; // allow this one, and remove the ones coming next. + else if(compressNextWhitespace == RemoveWhiteSpace) + continue; + + if (wsm == QTextHtmlParserNode::WhiteSpacePre + || textEditMode + ) { + if (ch == QLatin1Char('\n')) { + if (textEditMode) + continue; + } else if (ch == QLatin1Char('\r')) { + continue; + } + } else if (wsm != QTextHtmlParserNode::WhiteSpacePreWrap) { + compressNextWhitespace = RemoveWhiteSpace; + if (wsm == QTextHtmlParserNode::WhiteSpaceNoWrap) + ch = QChar::Nbsp; + else + ch = QLatin1Char(' '); + } + } else { + compressNextWhitespace = PreserveWhiteSpace; + } + + if (ch == QLatin1Char('\n') + || ch == QChar::ParagraphSeparator) { + + if (!textToInsert.isEmpty()) { + cursor.insertText(textToInsert, format); + textToInsert.clear(); + } + + QTextBlockFormat fmt = cursor.blockFormat(); + + if (fmt.hasProperty(QTextFormat::BlockBottomMargin)) { + QTextBlockFormat tmp = fmt; + tmp.clearProperty(QTextFormat::BlockBottomMargin); + cursor.setBlockFormat(tmp); + } + + fmt.clearProperty(QTextFormat::BlockTopMargin); + appendBlock(fmt, cursor.charFormat()); + } else { + if (!namedAnchors.isEmpty()) { + if (!textToInsert.isEmpty()) { + cursor.insertText(textToInsert, format); + textToInsert.clear(); + } + + format.setAnchor(true); + format.setAnchorNames(namedAnchors); + cursor.insertText(ch, format); + namedAnchors.clear(); + format.clearProperty(QTextFormat::IsAnchor); + format.clearProperty(QTextFormat::AnchorName); + } else { + textToInsert += ch; + } + } + } + + if (!textToInsert.isEmpty()) { + cursor.insertText(textToInsert, format); + } + + return cursor.position() != initialCursorPosition; +} + +QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processSpecialNodes() +{ + switch (currentNode->id) { + case Html_body: + if (currentNode->charFormat.background().style() != Qt::NoBrush) { + QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); + fmt.setBackground(currentNode->charFormat.background()); + doc->rootFrame()->setFrameFormat(fmt); + const_cast(currentNode)->charFormat.clearProperty(QTextFormat::BackgroundBrush); + } + compressNextWhitespace = RemoveWhiteSpace; + break; + + case Html_ol: + case Html_ul: { + QTextListFormat::Style style = currentNode->listStyle; + + if (currentNode->id == Html_ul && !currentNode->hasOwnListStyle && currentNode->parent) { + const QTextHtmlParserNode *n = &at(currentNode->parent); + while (n) { + if (n->id == Html_ul) { + style = nextListStyle(currentNode->listStyle); + } + if (n->parent) + n = &at(n->parent); + else + n = 0; + } + } + + QTextListFormat listFmt; + listFmt.setStyle(style); + if (!currentNode->textListNumberPrefix.isNull()) + listFmt.setNumberPrefix(currentNode->textListNumberPrefix); + if (!currentNode->textListNumberSuffix.isNull()) + listFmt.setNumberSuffix(currentNode->textListNumberSuffix); + + ++indent; + if (currentNode->hasCssListIndent) + listFmt.setIndent(currentNode->cssListIndent); + else + listFmt.setIndent(indent); + + List l; + l.format = listFmt; + l.listNode = currentNodeIdx; + lists.append(l); + compressNextWhitespace = RemoveWhiteSpace; + + // broken html:
      Text here
    • Foo + const QString simpl = currentNode->text.simplified(); + if (simpl.isEmpty() || simpl.at(0).isSpace()) + return ContinueWithNextNode; + break; + } + + case Html_table: { + Table t = scanTable(currentNodeIdx); + tables.append(t); + hasBlock = false; + compressNextWhitespace = RemoveWhiteSpace; + return ContinueWithNextNode; + } + + case Html_tr: + return ContinueWithNextNode; + + case Html_img: { + QTextImageFormat fmt; + fmt.setName(currentNode->imageName); + + fmt.merge(currentNode->charFormat); + + if (currentNode->imageWidth != -1) + fmt.setWidth(currentNode->imageWidth); + if (currentNode->imageHeight != -1) + fmt.setHeight(currentNode->imageHeight); + + cursor.insertImage(fmt, QTextFrameFormat::Position(currentNode->cssFloat)); + + cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor); + cursor.mergeCharFormat(currentNode->charFormat); + cursor.movePosition(QTextCursor::Right); + compressNextWhitespace = CollapseWhiteSpace; + + hasBlock = false; + return ContinueWithNextNode; + } + + case Html_hr: { + QTextBlockFormat blockFormat = currentNode->blockFormat; + blockFormat.setTopMargin(topMargin(currentNodeIdx)); + blockFormat.setBottomMargin(bottomMargin(currentNodeIdx)); + blockFormat.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, currentNode->width); + if (hasBlock && importMode == ImportToDocument) + cursor.mergeBlockFormat(blockFormat); + else + appendBlock(blockFormat); + hasBlock = false; + compressNextWhitespace = RemoveWhiteSpace; + return ContinueWithNextNode; + } + + default: break; + } + return ContinueWithCurrentNode; +} + +// returns true if a block tag was closed +bool QTextHtmlImporter::closeTag() +{ + const QTextHtmlParserNode *closedNode = &at(currentNodeIdx - 1); + const int endDepth = depth(currentNodeIdx) - 1; + int depth = this->depth(currentNodeIdx - 1); + bool blockTagClosed = false; + + while (depth > endDepth) { + Table *t = 0; + if (!tables.isEmpty()) + t = &tables.last(); + + switch (closedNode->id) { + case Html_tr: + if (t && !t->isTextFrame) { + ++t->currentRow; + + // for broken html with rowspans but missing tr tags + while (!t->currentCell.atEnd() && t->currentCell.row < t->currentRow) + ++t->currentCell; + } + + blockTagClosed = true; + break; + + case Html_table: + if (!t) + break; + indent = t->lastIndent; + + tables.resize(tables.size() - 1); + t = 0; + + if (tables.isEmpty()) { + cursor = doc->rootFrame()->lastCursorPosition(); + } else { + t = &tables.last(); + if (t->isTextFrame) + cursor = t->frame->lastCursorPosition(); + else if (!t->currentCell.atEnd()) + cursor = t->currentCell.cell().lastCursorPosition(); + } + + // we don't need an extra block after tables, so we don't + // claim to have closed one for the creation of a new one + // in import() + blockTagClosed = false; + compressNextWhitespace = RemoveWhiteSpace; + break; + + case Html_th: + case Html_td: + if (t && !t->isTextFrame) + ++t->currentCell; + blockTagClosed = true; + compressNextWhitespace = RemoveWhiteSpace; + break; + + case Html_ol: + case Html_ul: + if (lists.isEmpty()) + break; + lists.resize(lists.size() - 1); + --indent; + blockTagClosed = true; + break; + + case Html_br: + compressNextWhitespace = RemoveWhiteSpace; + break; + + case Html_div: + if (closedNode->children.isEmpty()) + break; + // fall through + default: + if (closedNode->isBlock()) + blockTagClosed = true; + break; + } + + closedNode = &at(closedNode->parent); + --depth; + } + + return blockTagClosed; +} + +QTextHtmlImporter::Table QTextHtmlImporter::scanTable(int tableNodeIdx) +{ + Table table; + table.columns = 0; + + QVector columnWidths; + + int tableHeaderRowCount = 0; + QVector rowNodes; + rowNodes.reserve(at(tableNodeIdx).children.count()); + foreach (int row, at(tableNodeIdx).children) + switch (at(row).id) { + case Html_tr: + rowNodes += row; + break; + case Html_thead: + case Html_tbody: + case Html_tfoot: + foreach (int potentialRow, at(row).children) + if (at(potentialRow).id == Html_tr) { + rowNodes += potentialRow; + if (at(row).id == Html_thead) + ++tableHeaderRowCount; + } + break; + default: break; + } + + QVector rowColSpans; + QVector rowColSpanForColumn; + + int effectiveRow = 0; + foreach (int row, rowNodes) { + int colsInRow = 0; + + foreach (int cell, at(row).children) + if (at(cell).isTableCell()) { + // skip all columns with spans from previous rows + while (colsInRow < rowColSpanForColumn.size()) { + const RowColSpanInfo &spanInfo = rowColSpanForColumn[colsInRow]; + + if (spanInfo.row + spanInfo.rowSpan > effectiveRow) { + Q_ASSERT(spanInfo.col == colsInRow); + colsInRow += spanInfo.colSpan; + } else + break; + } + + const QTextHtmlParserNode &c = at(cell); + const int currentColumn = colsInRow; + colsInRow += c.tableCellColSpan; + + RowColSpanInfo spanInfo; + spanInfo.row = effectiveRow; + spanInfo.col = currentColumn; + spanInfo.colSpan = c.tableCellColSpan; + spanInfo.rowSpan = c.tableCellRowSpan; + if (spanInfo.colSpan > 1 || spanInfo.rowSpan > 1) + rowColSpans.append(spanInfo); + + columnWidths.resize(qMax(columnWidths.count(), colsInRow)); + rowColSpanForColumn.resize(columnWidths.size()); + for (int i = currentColumn; i < currentColumn + c.tableCellColSpan; ++i) { + if (columnWidths.at(i).type() == QTextLength::VariableLength) { + QTextLength w = c.width; + if (c.tableCellColSpan > 1 && w.type() != QTextLength::VariableLength) + w = QTextLength(w.type(), w.value(100.) / c.tableCellColSpan); + columnWidths[i] = w; + } + rowColSpanForColumn[i] = spanInfo; + } + } + + table.columns = qMax(table.columns, colsInRow); + + ++effectiveRow; + } + table.rows = effectiveRow; + + table.lastIndent = indent; + indent = 0; + + if (table.rows == 0 || table.columns == 0) + return table; + + QTextFrameFormat fmt; + const QTextHtmlParserNode &node = at(tableNodeIdx); + + if (!node.isTextFrame) { + QTextTableFormat tableFmt; + tableFmt.setCellSpacing(node.tableCellSpacing); + tableFmt.setCellPadding(node.tableCellPadding); + if (node.blockFormat.hasProperty(QTextFormat::BlockAlignment)) + tableFmt.setAlignment(node.blockFormat.alignment()); + tableFmt.setColumns(table.columns); + tableFmt.setColumnWidthConstraints(columnWidths); + tableFmt.setHeaderRowCount(tableHeaderRowCount); + fmt = tableFmt; + } + + fmt.setTopMargin(topMargin(tableNodeIdx)); + fmt.setBottomMargin(bottomMargin(tableNodeIdx)); + fmt.setLeftMargin(leftMargin(tableNodeIdx) + + table.lastIndent * 40 // ##### not a good emulation + ); + fmt.setRightMargin(rightMargin(tableNodeIdx)); + + // compatibility + if (qFuzzyCompare(fmt.leftMargin(), fmt.rightMargin()) + && qFuzzyCompare(fmt.leftMargin(), fmt.topMargin()) + && qFuzzyCompare(fmt.leftMargin(), fmt.bottomMargin())) + fmt.setProperty(QTextFormat::FrameMargin, fmt.leftMargin()); + + fmt.setBorderStyle(node.borderStyle); + fmt.setBorderBrush(node.borderBrush); + fmt.setBorder(node.tableBorder); + fmt.setWidth(node.width); + fmt.setHeight(node.height); + if (node.blockFormat.hasProperty(QTextFormat::PageBreakPolicy)) + fmt.setPageBreakPolicy(node.blockFormat.pageBreakPolicy()); + + if (node.blockFormat.hasProperty(QTextFormat::LayoutDirection)) + fmt.setLayoutDirection(node.blockFormat.layoutDirection()); + if (node.charFormat.background().style() != Qt::NoBrush) + fmt.setBackground(node.charFormat.background()); + fmt.setPosition(QTextFrameFormat::Position(node.cssFloat)); + + if (node.isTextFrame) { + if (node.isRootFrame) { + table.frame = cursor.currentFrame(); + table.frame->setFrameFormat(fmt); + } else + table.frame = cursor.insertFrame(fmt); + + table.isTextFrame = true; + } else { + const int oldPos = cursor.position(); + QTextTable *textTable = cursor.insertTable(table.rows, table.columns, fmt.toTableFormat()); + table.frame = textTable; + + for (int i = 0; i < rowColSpans.count(); ++i) { + const RowColSpanInfo &nfo = rowColSpans.at(i); + textTable->mergeCells(nfo.row, nfo.col, nfo.rowSpan, nfo.colSpan); + } + + table.currentCell = TableCellIterator(textTable); + cursor.setPosition(oldPos); // restore for caption support which needs to be inserted right before the table + } + return table; +} + +QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processBlockNode() +{ + QTextBlockFormat block; + QTextCharFormat charFmt; + bool modifiedBlockFormat = true; + bool modifiedCharFormat = true; + + if (currentNode->isTableCell() && !tables.isEmpty()) { + Table &t = tables.last(); + if (!t.isTextFrame && !t.currentCell.atEnd()) { + QTextTableCell cell = t.currentCell.cell(); + if (cell.isValid()) { + QTextTableCellFormat fmt = cell.format().toTableCellFormat(); + if (topPadding(currentNodeIdx) >= 0) + fmt.setTopPadding(topPadding(currentNodeIdx)); + if (bottomPadding(currentNodeIdx) >= 0) + fmt.setBottomPadding(bottomPadding(currentNodeIdx)); + if (leftPadding(currentNodeIdx) >= 0) + fmt.setLeftPadding(leftPadding(currentNodeIdx)); + if (rightPadding(currentNodeIdx) >= 0) + fmt.setRightPadding(rightPadding(currentNodeIdx)); + cell.setFormat(fmt); + + cursor.setPosition(cell.firstPosition()); + } + } + hasBlock = true; + compressNextWhitespace = RemoveWhiteSpace; + + if (currentNode->charFormat.background().style() != Qt::NoBrush) { + charFmt.setBackground(currentNode->charFormat.background()); + cursor.mergeBlockCharFormat(charFmt); + } + } + + if (hasBlock) { + block = cursor.blockFormat(); + charFmt = cursor.blockCharFormat(); + modifiedBlockFormat = false; + modifiedCharFormat = false; + } + + // collapse + { + qreal tm = qreal(topMargin(currentNodeIdx)); + if (tm > block.topMargin()) { + block.setTopMargin(tm); + modifiedBlockFormat = true; + } + } + + int bottomMargin = this->bottomMargin(currentNodeIdx); + + // for list items we may want to collapse with the bottom margin of the + // list. + const QTextHtmlParserNode *parentNode = currentNode->parent ? &at(currentNode->parent) : 0; + if ((currentNode->id == Html_li || currentNode->id == Html_dt || currentNode->id == Html_dd) + && parentNode + && (parentNode->isListStart() || parentNode->id == Html_dl) + && (parentNode->children.last() == currentNodeIdx)) { + bottomMargin = qMax(bottomMargin, this->bottomMargin(currentNode->parent)); + } + + if (block.bottomMargin() != bottomMargin) { + block.setBottomMargin(bottomMargin); + modifiedBlockFormat = true; + } + + { + const qreal lm = leftMargin(currentNodeIdx); + const qreal rm = rightMargin(currentNodeIdx); + + if (block.leftMargin() != lm) { + block.setLeftMargin(lm); + modifiedBlockFormat = true; + } + if (block.rightMargin() != rm) { + block.setRightMargin(rm); + modifiedBlockFormat = true; + } + } + + if (currentNode->id != Html_li + && indent != 0 + && (lists.isEmpty() + || !hasBlock + || !lists.last().list + || lists.last().list->itemNumber(cursor.block()) == -1 + ) + ) { + block.setIndent(indent); + modifiedBlockFormat = true; + } + + if (currentNode->blockFormat.propertyCount() > 0) { + modifiedBlockFormat = true; + block.merge(currentNode->blockFormat); + } + + if (currentNode->charFormat.propertyCount() > 0) { + modifiedCharFormat = true; + charFmt.merge(currentNode->charFormat); + } + + // #################### + // block.setFloatPosition(node->cssFloat); + + if (wsm == QTextHtmlParserNode::WhiteSpacePre) { + block.setNonBreakableLines(true); + modifiedBlockFormat = true; + } + + if (currentNode->charFormat.background().style() != Qt::NoBrush && !currentNode->isTableCell()) { + block.setBackground(currentNode->charFormat.background()); + modifiedBlockFormat = true; + } + + if (hasBlock && (!currentNode->isEmptyParagraph || forceBlockMerging)) { + if (modifiedBlockFormat) + cursor.setBlockFormat(block); + if (modifiedCharFormat) + cursor.setBlockCharFormat(charFmt); + } else { + if (currentNodeIdx == 1 && cursor.position() == 0 && currentNode->isEmptyParagraph) { + cursor.setBlockFormat(block); + cursor.setBlockCharFormat(charFmt); + } else { + appendBlock(block, charFmt); + } + } + + if (currentNode->userState != -1) + cursor.block().setUserState(currentNode->userState); + + if (currentNode->id == Html_li && !lists.isEmpty()) { + List &l = lists.last(); + if (l.list) { + l.list->add(cursor.block()); + } else { + l.list = cursor.createList(l.format); + const qreal listTopMargin = topMargin(l.listNode); + if (listTopMargin > block.topMargin()) { + block.setTopMargin(listTopMargin); + cursor.mergeBlockFormat(block); + } + } + if (hasBlock) { + QTextBlockFormat fmt; + fmt.setIndent(0); + cursor.mergeBlockFormat(fmt); + } + } + + forceBlockMerging = false; + if (currentNode->id == Html_body || currentNode->id == Html_html) + forceBlockMerging = true; + + if (currentNode->isEmptyParagraph) { + hasBlock = false; + return ContinueWithNextNode; + } + + hasBlock = true; + blockTagClosed = false; + return ContinueWithCurrentNode; +} + +void QTextHtmlImporter::appendBlock(const QTextBlockFormat &format, QTextCharFormat charFmt) +{ + if (!namedAnchors.isEmpty()) { + charFmt.setAnchor(true); + charFmt.setAnchorNames(namedAnchors); + namedAnchors.clear(); + } + + cursor.insertBlock(format, charFmt); + + if (wsm != QTextHtmlParserNode::WhiteSpacePre && wsm != QTextHtmlParserNode::WhiteSpacePreWrap) + compressNextWhitespace = RemoveWhiteSpace; +} + +#endif // QT_NO_TEXTHTMLPARSER + +/*! + \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text) + + Returns a QTextDocumentFragment based on the arbitrary piece of + HTML in the given \a text. The formatting is preserved as much as + possible; for example, "bold" will become a document + fragment with the text "bold" with a bold character format. +*/ + +#ifndef QT_NO_TEXTHTMLPARSER + +QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html) +{ + return fromHtml(html, 0); +} + +/*! + \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text, const QTextDocument *resourceProvider) + \since 4.2 + + Returns a QTextDocumentFragment based on the arbitrary piece of + HTML in the given \a text. The formatting is preserved as much as + possible; for example, "bold" will become a document + fragment with the text "bold" with a bold character format. + + If the provided HTML contains references to external resources such as imported style sheets, then + they will be loaded through the \a resourceProvider. +*/ + +QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html, const QTextDocument *resourceProvider) +{ + QTextDocumentFragment res; + res.d = new QTextDocumentFragmentPrivate; + + QTextHtmlImporter importer(res.d->doc, html, QTextHtmlImporter::ImportToFragment, resourceProvider); + importer.import(); + return res; +} + +QT_END_NAMESPACE +#endif // QT_NO_TEXTHTMLPARSER diff --git a/src/gui/text/qtextdocumentfragment.h b/src/gui/text/qtextdocumentfragment.h new file mode 100644 index 0000000000..976c538d0f --- /dev/null +++ b/src/gui/text/qtextdocumentfragment.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTDOCUMENTFRAGMENT_H +#define QTEXTDOCUMENTFRAGMENT_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QTextStream; +class QTextDocument; +class QTextDocumentFragmentPrivate; +class QTextCursor; + +class Q_GUI_EXPORT QTextDocumentFragment +{ +public: + QTextDocumentFragment(); + explicit QTextDocumentFragment(const QTextDocument *document); + explicit QTextDocumentFragment(const QTextCursor &range); + QTextDocumentFragment(const QTextDocumentFragment &rhs); + QTextDocumentFragment &operator=(const QTextDocumentFragment &rhs); + ~QTextDocumentFragment(); + + bool isEmpty() const; + + QString toPlainText() const; +#ifndef QT_NO_TEXTHTMLPARSER + QString toHtml() const; + QString toHtml(const QByteArray &encoding) const; +#endif // QT_NO_TEXTHTMLPARSER + + static QTextDocumentFragment fromPlainText(const QString &plainText); +#ifndef QT_NO_TEXTHTMLPARSER + static QTextDocumentFragment fromHtml(const QString &html); + static QTextDocumentFragment fromHtml(const QString &html, const QTextDocument *resourceProvider); +#endif // QT_NO_TEXTHTMLPARSER + +private: + QTextDocumentFragmentPrivate *d; + friend class QTextCursor; + friend class QTextDocumentWriter; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTEXTDOCUMENTFRAGMENT_H diff --git a/src/gui/text/qtextdocumentfragment_p.h b/src/gui/text/qtextdocumentfragment_p.h new file mode 100644 index 0000000000..5b04862f54 --- /dev/null +++ b/src/gui/text/qtextdocumentfragment_p.h @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTDOCUMENTFRAGMENT_P_H +#define QTEXTDOCUMENTFRAGMENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qtextdocument.h" +#include "private/qtexthtmlparser_p.h" +#include "private/qtextdocument_p.h" +#include "QtGui/qtexttable.h" +#include "QtCore/qatomic.h" +#include "QtCore/qlist.h" +#include "QtCore/qmap.h" +#include "QtCore/qpointer.h" +#include "QtCore/qvarlengtharray.h" +#include "QtCore/qdatastream.h" + +QT_BEGIN_NAMESPACE + +class QTextDocumentFragmentPrivate; + +class QTextCopyHelper +{ +public: + QTextCopyHelper(const QTextCursor &_source, const QTextCursor &_destination, bool forceCharFormat = false, const QTextCharFormat &fmt = QTextCharFormat()); + + void copy(); + +private: + void appendFragments(int pos, int endPos); + int appendFragment(int pos, int endPos, int objectIndex = -1); + int convertFormatIndex(const QTextFormat &oldFormat, int objectIndexToSet = -1); + inline int convertFormatIndex(int oldFormatIndex, int objectIndexToSet = -1) + { return convertFormatIndex(src->formatCollection()->format(oldFormatIndex), objectIndexToSet); } + inline QTextFormat convertFormat(const QTextFormat &fmt) + { return dst->formatCollection()->format(convertFormatIndex(fmt)); } + + int insertPos; + + bool forceCharFormat; + int primaryCharFormatIndex; + + QTextCursor cursor; + QTextDocumentPrivate *dst; + QTextDocumentPrivate *src; + QTextFormatCollection &formatCollection; + const QString originalText; + QMap objectIndexMap; +}; + +class QTextDocumentFragmentPrivate +{ +public: + QTextDocumentFragmentPrivate(const QTextCursor &cursor = QTextCursor()); + inline ~QTextDocumentFragmentPrivate() { delete doc; } + + void insert(QTextCursor &cursor) const; + + QAtomicInt ref; + QTextDocument *doc; + + uint importedFromPlainText : 1; +private: + Q_DISABLE_COPY(QTextDocumentFragmentPrivate) +}; + +#ifndef QT_NO_TEXTHTMLPARSER + +class QTextHtmlImporter : public QTextHtmlParser +{ + struct Table; +public: + enum ImportMode { + ImportToFragment, + ImportToDocument + }; + + QTextHtmlImporter(QTextDocument *_doc, const QString &html, + ImportMode mode, + const QTextDocument *resourceProvider = 0); + + void import(); + +private: + bool closeTag(); + + Table scanTable(int tableNodeIdx); + + enum ProcessNodeResult { ContinueWithNextNode, ContinueWithCurrentNode }; + + void appendBlock(const QTextBlockFormat &format, QTextCharFormat charFmt = QTextCharFormat()); + bool appendNodeText(); + + ProcessNodeResult processBlockNode(); + ProcessNodeResult processSpecialNodes(); + + struct List + { + inline List() : listNode(0) {} + QTextListFormat format; + int listNode; + QPointer list; + }; + QVector lists; + int indent; + + // insert a named anchor the next time we emit a char format, + // either in a block or in regular text + QStringList namedAnchors; + +#ifdef Q_CC_SUN + friend struct QTextHtmlImporter::Table; +#endif + struct TableCellIterator + { + inline TableCellIterator(QTextTable *t = 0) : table(t), row(0), column(0) {} + + inline TableCellIterator &operator++() { + if (atEnd()) + return *this; + do { + const QTextTableCell cell = table->cellAt(row, column); + if (!cell.isValid()) + break; + column += cell.columnSpan(); + if (column >= table->columns()) { + column = 0; + ++row; + } + } while (row < table->rows() && table->cellAt(row, column).row() != row); + + return *this; + } + + inline bool atEnd() const { return table == 0 || row >= table->rows(); } + + QTextTableCell cell() const { return table->cellAt(row, column); } + + QTextTable *table; + int row; + int column; + }; + + friend struct Table; + struct Table + { + Table() : isTextFrame(false), rows(0), columns(0), currentRow(0), lastIndent(0) {} + QPointer frame; + bool isTextFrame; + int rows; + int columns; + int currentRow; // ... for buggy html (see html_skipCell testcase) + TableCellIterator currentCell; + int lastIndent; + }; + QVector
tables; + + struct RowColSpanInfo + { + int row, col; + int rowSpan, colSpan; + }; + + enum WhiteSpace + { + RemoveWhiteSpace, + CollapseWhiteSpace, + PreserveWhiteSpace + }; + + WhiteSpace compressNextWhitespace; + + QTextDocument *doc; + QTextCursor cursor; + QTextHtmlParserNode::WhiteSpaceMode wsm; + ImportMode importMode; + bool hasBlock; + bool forceBlockMerging; + bool blockTagClosed; + int currentNodeIdx; + const QTextHtmlParserNode *currentNode; +}; + +QT_END_NAMESPACE +#endif // QT_NO_TEXTHTMLPARSER + +#endif // QTEXTDOCUMENTFRAGMENT_P_H diff --git a/src/gui/text/qtextdocumentlayout.cpp b/src/gui/text/qtextdocumentlayout.cpp new file mode 100644 index 0000000000..ce157be254 --- /dev/null +++ b/src/gui/text/qtextdocumentlayout.cpp @@ -0,0 +1,3263 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextdocumentlayout_p.h" +#include "qtextdocument_p.h" +#include "qtextimagehandler_p.h" +#include "qtexttable.h" +#include "qtextlist.h" +#include "qtextengine_p.h" +#include "private/qcssutil_p.h" + +#include "qabstracttextdocumentlayout_p.h" +#include "qcssparser_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "private/qfunctions_p.h" + +// #define LAYOUT_DEBUG + +#ifdef LAYOUT_DEBUG +#define LDEBUG qDebug() +#define INC_INDENT debug_indent += " " +#define DEC_INDENT debug_indent = debug_indent.left(debug_indent.length()-2) +#else +#define LDEBUG if(0) qDebug() +#define INC_INDENT do {} while(0) +#define DEC_INDENT do {} while(0) +#endif + +QT_BEGIN_NAMESPACE + +// ################ should probably add frameFormatChange notification! + +struct QTextLayoutStruct; + +class QTextFrameData : public QTextFrameLayoutData +{ +public: + QTextFrameData(); + + // relative to parent frame + QFixedPoint position; + QFixedSize size; + + // contents starts at (margin+border/margin+border) + QFixed topMargin; + QFixed bottomMargin; + QFixed leftMargin; + QFixed rightMargin; + QFixed border; + QFixed padding; + // contents width includes padding (as we need to treat this on a per cell basis for tables) + QFixed contentsWidth; + QFixed contentsHeight; + QFixed oldContentsWidth; + + // accumulated margins + QFixed effectiveTopMargin; + QFixed effectiveBottomMargin; + + QFixed minimumWidth; + QFixed maximumWidth; + + QTextLayoutStruct *currentLayoutStruct; + + bool sizeDirty; + bool layoutDirty; + + QList > floats; +}; + +QTextFrameData::QTextFrameData() + : maximumWidth(QFIXED_MAX), + currentLayoutStruct(0), sizeDirty(true), layoutDirty(true) +{ +} + +struct QTextLayoutStruct { + QTextLayoutStruct() : maximumWidth(QFIXED_MAX), fullLayout(false) + {} + QTextFrame *frame; + QFixed x_left; + QFixed x_right; + QFixed frameY; // absolute y position of the current frame + QFixed y; // always relative to the current frame + QFixed contentsWidth; + QFixed minimumWidth; + QFixed maximumWidth; + bool fullLayout; + QList pendingFloats; + QFixed pageHeight; + QFixed pageBottom; + QFixed pageTopMargin; + QFixed pageBottomMargin; + QRectF updateRect; + QRectF updateRectForFloats; + + inline void addUpdateRectForFloat(const QRectF &rect) { + if (updateRectForFloats.isValid()) + updateRectForFloats |= rect; + else + updateRectForFloats = rect; + } + + inline QFixed absoluteY() const + { return frameY + y; } + + inline int currentPage() const + { return pageHeight == 0 ? 0 : (absoluteY() / pageHeight).truncate(); } + + inline void newPage() + { if (pageHeight == QFIXED_MAX) return; pageBottom += pageHeight; y = pageBottom - pageHeight + pageBottomMargin + pageTopMargin - frameY; } +}; + +class QTextTableData : public QTextFrameData +{ +public: + QFixed cellSpacing, cellPadding; + qreal deviceScale; + QVector minWidths; + QVector maxWidths; + QVector widths; + QVector heights; + QVector columnPositions; + QVector rowPositions; + + QVector cellVerticalOffsets; + + QFixed headerHeight; + + // maps from cell index (row + col * rowCount) to child frames belonging to + // the specific cell + QMultiHash childFrameMap; + + inline QFixed cellWidth(int column, int colspan) const + { return columnPositions.at(column + colspan - 1) + widths.at(column + colspan - 1) + - columnPositions.at(column); } + + inline void calcRowPosition(int row) + { + if (row > 0) + rowPositions[row] = rowPositions.at(row - 1) + heights.at(row - 1) + border + cellSpacing + border; + } + + QRectF cellRect(const QTextTableCell &cell) const; + + inline QFixed paddingProperty(const QTextFormat &format, QTextFormat::Property property) const + { + QVariant v = format.property(property); + if (v.isNull()) { + return cellPadding; + } else { + Q_ASSERT(v.userType() == QVariant::Double || v.userType() == QMetaType::Float); + return QFixed::fromReal(v.toReal() * deviceScale); + } + } + + inline QFixed topPadding(const QTextFormat &format) const + { + return paddingProperty(format, QTextFormat::TableCellTopPadding); + } + + inline QFixed bottomPadding(const QTextFormat &format) const + { + return paddingProperty(format, QTextFormat::TableCellBottomPadding); + } + + inline QFixed leftPadding(const QTextFormat &format) const + { + return paddingProperty(format, QTextFormat::TableCellLeftPadding); + } + + inline QFixed rightPadding(const QTextFormat &format) const + { + return paddingProperty(format, QTextFormat::TableCellRightPadding); + } + + inline QFixedPoint cellPosition(const QTextTableCell &cell) const + { + const QTextFormat fmt = cell.format(); + return cellPosition(cell.row(), cell.column()) + QFixedPoint(leftPadding(fmt), topPadding(fmt)); + } + + void updateTableSize(); + +private: + inline QFixedPoint cellPosition(int row, int col) const + { return QFixedPoint(columnPositions.at(col), rowPositions.at(row) + cellVerticalOffsets.at(col + row * widths.size())); } +}; + +static QTextFrameData *createData(QTextFrame *f) +{ + QTextFrameData *data; + if (qobject_cast(f)) + data = new QTextTableData; + else + data = new QTextFrameData; + f->setLayoutData(data); + return data; +} + +static inline QTextFrameData *data(QTextFrame *f) +{ + QTextFrameData *data = static_cast(f->layoutData()); + if (!data) + data = createData(f); + return data; +} + +static bool isFrameFromInlineObject(QTextFrame *f) +{ + return f->firstPosition() > f->lastPosition(); +} + +void QTextTableData::updateTableSize() +{ + const QFixed effectiveTopMargin = this->topMargin + border + padding; + const QFixed effectiveBottomMargin = this->bottomMargin + border + padding; + const QFixed effectiveLeftMargin = this->leftMargin + border + padding; + const QFixed effectiveRightMargin = this->rightMargin + border + padding; + size.height = contentsHeight == -1 + ? rowPositions.last() + heights.last() + padding + border + cellSpacing + effectiveBottomMargin + : effectiveTopMargin + contentsHeight + effectiveBottomMargin; + size.width = effectiveLeftMargin + contentsWidth + effectiveRightMargin; +} + +QRectF QTextTableData::cellRect(const QTextTableCell &cell) const +{ + const int row = cell.row(); + const int rowSpan = cell.rowSpan(); + const int column = cell.column(); + const int colSpan = cell.columnSpan(); + + return QRectF(columnPositions.at(column).toReal(), + rowPositions.at(row).toReal(), + (columnPositions.at(column + colSpan - 1) + widths.at(column + colSpan - 1) - columnPositions.at(column)).toReal(), + (rowPositions.at(row + rowSpan - 1) + heights.at(row + rowSpan - 1) - rowPositions.at(row)).toReal()); +} + +static inline bool isEmptyBlockBeforeTable(const QTextBlock &block, const QTextBlockFormat &format, const QTextFrame::Iterator &nextIt) +{ + return !nextIt.atEnd() + && qobject_cast(nextIt.currentFrame()) + && block.isValid() + && block.length() == 1 + && !format.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth) + && !format.hasProperty(QTextFormat::BackgroundBrush) + && nextIt.currentFrame()->firstPosition() == block.position() + 1 + ; +} + +static inline bool isEmptyBlockBeforeTable(QTextFrame::Iterator it) +{ + QTextFrame::Iterator next = it; ++next; + if (it.currentFrame()) + return false; + QTextBlock block = it.currentBlock(); + return isEmptyBlockBeforeTable(block, block.blockFormat(), next); +} + +static inline bool isEmptyBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame) +{ + return qobject_cast(previousFrame) + && block.isValid() + && block.length() == 1 + && previousFrame->lastPosition() == block.position() - 1 + ; +} + +static inline bool isLineSeparatorBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame) +{ + return qobject_cast(previousFrame) + && block.isValid() + && block.length() > 1 + && block.text().at(0) == QChar::LineSeparator + && previousFrame->lastPosition() == block.position() - 1 + ; +} + +/* + +Optimization strategies: + +HTML layout: + +* Distinguish between normal and special flow. For normal flow the condition: + y1 > y2 holds for all blocks with b1.key() > b2.key(). +* Special flow is: floats, table cells + +* Normal flow within table cells. Tables (not cells) are part of the normal flow. + + +* If blocks grows/shrinks in height and extends over whole page width at the end, move following blocks. +* If height doesn't change, no need to do anything + +Table cells: + +* If minWidth of cell changes, recalculate table width, relayout if needed. +* What about maxWidth when doing auto layout? + +Floats: +* need fixed or proportional width, otherwise don't float! +* On width/height change relayout surrounding paragraphs. + +Document width change: +* full relayout needed + + +Float handling: + +* Floats are specified by a special format object. +* currently only floating images are implemented. + +*/ + +/* + + On the table layouting: + + +---[ table border ]------------------------- + | [ cell spacing ] + | +------[ cell border ]-----+ +-------- + | | | | + | | + | | + | | + | + + rowPositions[i] and columnPositions[i] point at the cell content + position. So for example the left border is drawn at + x = columnPositions[i] - fd->border and similar for y. + +*/ + +struct QCheckPoint +{ + QFixed y; + QFixed frameY; // absolute y position of the current frame + int positionInFrame; + QFixed minimumWidth; + QFixed maximumWidth; + QFixed contentsWidth; +}; +Q_DECLARE_TYPEINFO(QCheckPoint, Q_PRIMITIVE_TYPE); + +Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCheckPoint &checkPoint, QFixed y) +{ + return checkPoint.y < y; +} + +Q_STATIC_GLOBAL_OPERATOR bool operator<(const QCheckPoint &checkPoint, int pos) +{ + return checkPoint.positionInFrame < pos; +} + +static void fillBackground(QPainter *p, const QRectF &rect, QBrush brush, const QPointF &origin, QRectF gradientRect = QRectF()) +{ + p->save(); + if (brush.style() >= Qt::LinearGradientPattern && brush.style() <= Qt::ConicalGradientPattern) { + if (!gradientRect.isNull()) { + QTransform m; + m.translate(gradientRect.left(), gradientRect.top()); + m.scale(gradientRect.width(), gradientRect.height()); + brush.setTransform(m); + const_cast(brush.gradient())->setCoordinateMode(QGradient::LogicalMode); + } + } else { + p->setBrushOrigin(origin); + } + p->fillRect(rect, brush); + p->restore(); +} + +class QTextDocumentLayoutPrivate : public QAbstractTextDocumentLayoutPrivate +{ + Q_DECLARE_PUBLIC(QTextDocumentLayout) +public: + QTextDocumentLayoutPrivate(); + + QTextOption::WrapMode wordWrapMode; +#ifdef LAYOUT_DEBUG + mutable QString debug_indent; +#endif + + int fixedColumnWidth; + int cursorWidth; + + QSizeF lastReportedSize; + QRectF viewportRect; + QRectF clipRect; + + mutable int currentLazyLayoutPosition; + mutable int lazyLayoutStepSize; + QBasicTimer layoutTimer; + mutable QBasicTimer sizeChangedTimer; + uint showLayoutProgress : 1; + uint insideDocumentChange : 1; + + int lastPageCount; + qreal idealWidth; + bool contentHasAlignment; + + QFixed blockIndent(const QTextBlockFormat &blockFormat) const; + + void drawFrame(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context, + QTextFrame *f) const; + void drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context, + QTextFrame::Iterator it, const QList &floats, QTextBlock *cursorBlockNeedingRepaint) const; + void drawBlock(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context, + QTextBlock bl, bool inRootFrame) const; + void drawListItem(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context, + QTextBlock bl, const QTextCharFormat *selectionFormat) const; + void drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context, + QTextTable *table, QTextTableData *td, int r, int c, + QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const; + void drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin, qreal border, + const QBrush &brush, QTextFrameFormat::BorderStyle style) const; + void drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const; + + enum HitPoint { + PointBefore, + PointAfter, + PointInside, + PointExact + }; + HitPoint hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const; + HitPoint hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p, + int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const; + HitPoint hitTest(QTextTable *table, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const; + HitPoint hitTest(QTextBlock bl, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const; + + QTextLayoutStruct layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width, + int layoutFrom, int layoutTo, QTextTableData *tableData, QFixed absoluteTableY, + bool withPageBreaks); + void setCellPosition(QTextTable *t, const QTextTableCell &cell, const QPointF &pos); + QRectF layoutTable(QTextTable *t, int layoutFrom, int layoutTo, QFixed parentY); + + void positionFloat(QTextFrame *frame, QTextLine *currentLine = 0); + + // calls the next one + QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY = 0); + QRectF layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY = 0); + + void layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat, + QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat); + void layoutFlow(QTextFrame::Iterator it, QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, QFixed width = 0); + void pageBreakInsideTable(QTextTable *table, QTextLayoutStruct *layoutStruct); + + + void floatMargins(const QFixed &y, const QTextLayoutStruct *layoutStruct, QFixed *left, QFixed *right) const; + QFixed findY(QFixed yFrom, const QTextLayoutStruct *layoutStruct, QFixed requiredWidth) const; + + QVector checkPoints; + + QTextFrame::Iterator frameIteratorForYPosition(QFixed y) const; + QTextFrame::Iterator frameIteratorForTextPosition(int position) const; + + void ensureLayouted(QFixed y) const; + void ensureLayoutedByPosition(int position) const; + inline void ensureLayoutFinished() const + { ensureLayoutedByPosition(INT_MAX); } + void layoutStep() const; + + QRectF frameBoundingRectInternal(QTextFrame *frame) const; + + qreal scaleToDevice(qreal value) const; + QFixed scaleToDevice(QFixed value) const; +}; + +QTextDocumentLayoutPrivate::QTextDocumentLayoutPrivate() + : fixedColumnWidth(-1), + cursorWidth(1), + currentLazyLayoutPosition(-1), + lazyLayoutStepSize(1000), + lastPageCount(-1) +{ + showLayoutProgress = true; + insideDocumentChange = false; + idealWidth = 0; + contentHasAlignment = false; +} + +QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForYPosition(QFixed y) const +{ + QTextFrame *rootFrame = document->rootFrame(); + + if (checkPoints.isEmpty() + || y < 0 || y > data(rootFrame)->size.height) + return rootFrame->begin(); + + QVector::ConstIterator checkPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), y); + if (checkPoint == checkPoints.end()) + return rootFrame->begin(); + + if (checkPoint != checkPoints.begin()) + --checkPoint; + + const int position = rootFrame->firstPosition() + checkPoint->positionInFrame; + return frameIteratorForTextPosition(position); +} + +QTextFrame::Iterator QTextDocumentLayoutPrivate::frameIteratorForTextPosition(int position) const +{ + QTextFrame *rootFrame = docPrivate->rootFrame(); + + const QTextDocumentPrivate::BlockMap &map = docPrivate->blockMap(); + const int begin = map.findNode(rootFrame->firstPosition()); + const int end = map.findNode(rootFrame->lastPosition()+1); + + const int block = map.findNode(position); + const int blockPos = map.position(block); + + QTextFrame::iterator it(rootFrame, block, begin, end); + + QTextFrame *containingFrame = docPrivate->frameAt(blockPos); + if (containingFrame != rootFrame) { + while (containingFrame->parentFrame() != rootFrame) { + containingFrame = containingFrame->parentFrame(); + Q_ASSERT(containingFrame); + } + + it.cf = containingFrame; + it.cb = 0; + } + + return it; +} + +QTextDocumentLayoutPrivate::HitPoint +QTextDocumentLayoutPrivate::hitTest(QTextFrame *frame, const QFixedPoint &point, int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const +{ + QTextFrameData *fd = data(frame); + // ######### + if (fd->layoutDirty) + return PointAfter; + Q_ASSERT(!fd->layoutDirty); + Q_ASSERT(!fd->sizeDirty); + const QFixedPoint relativePoint(point.x - fd->position.x, point.y - fd->position.y); + + QTextFrame *rootFrame = docPrivate->rootFrame(); + +// LDEBUG << "checking frame" << frame->firstPosition() << "point=" << point +// << "position" << fd->position << "size" << fd->size; + if (frame != rootFrame) { + if (relativePoint.y < 0 || relativePoint.x < 0) { + *position = frame->firstPosition() - 1; +// LDEBUG << "before pos=" << *position; + return PointBefore; + } else if (relativePoint.y > fd->size.height || relativePoint.x > fd->size.width) { + *position = frame->lastPosition() + 1; +// LDEBUG << "after pos=" << *position; + return PointAfter; + } + } + + if (isFrameFromInlineObject(frame)) { + *position = frame->firstPosition() - 1; + return PointExact; + } + + if (QTextTable *table = qobject_cast(frame)) { + const int rows = table->rows(); + const int columns = table->columns(); + QTextTableData *td = static_cast(data(table)); + + if (!td->childFrameMap.isEmpty()) { + for (int r = 0; r < rows; ++r) { + for (int c = 0; c < columns; ++c) { + QTextTableCell cell = table->cellAt(r, c); + if (cell.row() != r || cell.column() != c) + continue; + + QRectF cellRect = td->cellRect(cell); + const QFixedPoint cellPos = QFixedPoint::fromPointF(cellRect.topLeft()); + const QFixedPoint pointInCell = relativePoint - cellPos; + + const QList childFrames = td->childFrameMap.values(r + c * rows); + for (int i = 0; i < childFrames.size(); ++i) { + QTextFrame *child = childFrames.at(i); + if (isFrameFromInlineObject(child) + && child->frameFormat().position() != QTextFrameFormat::InFlow + && hitTest(child, pointInCell, position, l, accuracy) == PointExact) + { + return PointExact; + } + } + } + } + } + + return hitTest(table, relativePoint, position, l, accuracy); + } + + const QList childFrames = frame->childFrames(); + for (int i = 0; i < childFrames.size(); ++i) { + QTextFrame *child = childFrames.at(i); + if (isFrameFromInlineObject(child) + && child->frameFormat().position() != QTextFrameFormat::InFlow + && hitTest(child, relativePoint, position, l, accuracy) == PointExact) + { + return PointExact; + } + } + + QTextFrame::Iterator it = frame->begin(); + + if (frame == rootFrame) { + it = frameIteratorForYPosition(relativePoint.y); + + Q_ASSERT(it.parentFrame() == frame); + } + + if (it.currentFrame()) + *position = it.currentFrame()->firstPosition(); + else + *position = it.currentBlock().position(); + + return hitTest(it, PointBefore, relativePoint, position, l, accuracy); +} + +QTextDocumentLayoutPrivate::HitPoint +QTextDocumentLayoutPrivate::hitTest(QTextFrame::Iterator it, HitPoint hit, const QFixedPoint &p, + int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const +{ + INC_INDENT; + + for (; !it.atEnd(); ++it) { + QTextFrame *c = it.currentFrame(); + HitPoint hp; + int pos = -1; + if (c) { + hp = hitTest(c, p, &pos, l, accuracy); + } else { + hp = hitTest(it.currentBlock(), p, &pos, l, accuracy); + } + if (hp >= PointInside) { + if (isEmptyBlockBeforeTable(it)) + continue; + hit = hp; + *position = pos; + break; + } + if (hp == PointBefore && pos < *position) { + *position = pos; + hit = hp; + } else if (hp == PointAfter && pos > *position) { + *position = pos; + hit = hp; + } + } + + DEC_INDENT; +// LDEBUG << "inside=" << hit << " pos=" << *position; + return hit; +} + +QTextDocumentLayoutPrivate::HitPoint +QTextDocumentLayoutPrivate::hitTest(QTextTable *table, const QFixedPoint &point, + int *position, QTextLayout **l, Qt::HitTestAccuracy accuracy) const +{ + QTextTableData *td = static_cast(data(table)); + + QVector::ConstIterator rowIt = qLowerBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), point.y); + if (rowIt == td->rowPositions.constEnd()) { + rowIt = td->rowPositions.constEnd() - 1; + } else if (rowIt != td->rowPositions.constBegin()) { + --rowIt; + } + + QVector::ConstIterator colIt = qLowerBound(td->columnPositions.constBegin(), td->columnPositions.constEnd(), point.x); + if (colIt == td->columnPositions.constEnd()) { + colIt = td->columnPositions.constEnd() - 1; + } else if (colIt != td->columnPositions.constBegin()) { + --colIt; + } + + QTextTableCell cell = table->cellAt(rowIt - td->rowPositions.constBegin(), + colIt - td->columnPositions.constBegin()); + if (!cell.isValid()) + return PointBefore; + + *position = cell.firstPosition(); + + HitPoint hp = hitTest(cell.begin(), PointInside, point - td->cellPosition(cell), position, l, accuracy); + + if (hp == PointExact) + return hp; + if (hp == PointAfter) + *position = cell.lastPosition(); + return PointInside; +} + +QTextDocumentLayoutPrivate::HitPoint +QTextDocumentLayoutPrivate::hitTest(QTextBlock bl, const QFixedPoint &point, int *position, QTextLayout **l, + Qt::HitTestAccuracy accuracy) const +{ + QTextLayout *tl = bl.layout(); + QRectF textrect = tl->boundingRect(); + textrect.translate(tl->position()); +// LDEBUG << " checking block" << bl.position() << "point=" << point +// << " tlrect" << textrect; + *position = bl.position(); + if (point.y.toReal() < textrect.top()) { +// LDEBUG << " before pos=" << *position; + return PointBefore; + } else if (point.y.toReal() > textrect.bottom()) { + *position += bl.length(); +// LDEBUG << " after pos=" << *position; + return PointAfter; + } + + QPointF pos = point.toPointF() - tl->position(); + + // ### rtl? + + HitPoint hit = PointInside; + *l = tl; + int off = 0; + for (int i = 0; i < tl->lineCount(); ++i) { + QTextLine line = tl->lineAt(i); + const QRectF lr = line.naturalTextRect(); + if (lr.top() > pos.y()) { + off = qMin(off, line.textStart()); + } else if (lr.bottom() <= pos.y()) { + off = qMax(off, line.textStart() + line.textLength()); + } else { + if (lr.left() <= pos.x() && lr.right() >= pos.x()) + hit = PointExact; + // when trying to hit an anchor we want it to hit not only in the left + // half + if (accuracy == Qt::ExactHit) + off = line.xToCursor(pos.x(), QTextLine::CursorOnCharacter); + else + off = line.xToCursor(pos.x(), QTextLine::CursorBetweenCharacters); + break; + } + } + *position += off; + +// LDEBUG << " inside=" << hit << " pos=" << *position; + return hit; +} + +// ### could be moved to QTextBlock +QFixed QTextDocumentLayoutPrivate::blockIndent(const QTextBlockFormat &blockFormat) const +{ + qreal indent = blockFormat.indent(); + + QTextObject *object = document->objectForFormat(blockFormat); + if (object) + indent += object->format().toListFormat().indent(); + + if (qIsNull(indent)) + return 0; + + qreal scale = 1; + if (paintDevice) { + scale = qreal(paintDevice->logicalDpiY()) / qreal(qt_defaultDpi()); + } + + return QFixed::fromReal(indent * scale * document->indentWidth()); +} + +void QTextDocumentLayoutPrivate::drawBorder(QPainter *painter, const QRectF &rect, qreal topMargin, qreal bottomMargin, + qreal border, const QBrush &brush, QTextFrameFormat::BorderStyle style) const +{ + const qreal pageHeight = document->pageSize().height(); + const int topPage = pageHeight > 0 ? static_cast(rect.top() / pageHeight) : 0; + const int bottomPage = pageHeight > 0 ? static_cast((rect.bottom() + border) / pageHeight) : 0; + +#ifndef QT_NO_CSSPARSER + QCss::BorderStyle cssStyle = static_cast(style + 1); +#endif //QT_NO_CSSPARSER + + bool turn_off_antialiasing = !(painter->renderHints() & QPainter::Antialiasing); + painter->setRenderHint(QPainter::Antialiasing); + + for (int i = topPage; i <= bottomPage; ++i) { + QRectF clipped = rect.toRect(); + + if (topPage != bottomPage) { + clipped.setTop(qMax(clipped.top(), i * pageHeight + topMargin - border)); + clipped.setBottom(qMin(clipped.bottom(), (i + 1) * pageHeight - bottomMargin)); + + if (clipped.bottom() <= clipped.top()) + continue; + } +#ifndef QT_NO_CSSPARSER + qDrawEdge(painter, clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border, 0, 0, QCss::LeftEdge, cssStyle, brush); + qDrawEdge(painter, clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border, 0, 0, QCss::TopEdge, cssStyle, brush); + qDrawEdge(painter, clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom(), 0, 0, QCss::RightEdge, cssStyle, brush); + qDrawEdge(painter, clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border, 0, 0, QCss::BottomEdge, cssStyle, brush); +#else + painter->save(); + painter->setPen(Qt::NoPen); + painter->setBrush(brush); + painter->drawRect(QRectF(clipped.left(), clipped.top(), clipped.left() + border, clipped.bottom() + border)); + painter->drawRect(QRectF(clipped.left() + border, clipped.top(), clipped.right() + border, clipped.top() + border)); + painter->drawRect(QRectF(clipped.right(), clipped.top() + border, clipped.right() + border, clipped.bottom())); + painter->drawRect(QRectF(clipped.left() + border, clipped.bottom(), clipped.right() + border, clipped.bottom() + border)); + painter->restore(); +#endif //QT_NO_CSSPARSER + } + if (turn_off_antialiasing) + painter->setRenderHint(QPainter::Antialiasing, false); +} + +void QTextDocumentLayoutPrivate::drawFrameDecoration(QPainter *painter, QTextFrame *frame, QTextFrameData *fd, const QRectF &clip, const QRectF &rect) const +{ + + const QBrush bg = frame->frameFormat().background(); + if (bg != Qt::NoBrush) { + QRectF bgRect = rect; + bgRect.adjust((fd->leftMargin + fd->border).toReal(), + (fd->topMargin + fd->border).toReal(), + - (fd->rightMargin + fd->border).toReal(), + - (fd->bottomMargin + fd->border).toReal()); + + QRectF gradientRect; // invalid makes it default to bgRect + QPointF origin = bgRect.topLeft(); + if (!frame->parentFrame()) { + bgRect = clip; + gradientRect.setWidth(painter->device()->width()); + gradientRect.setHeight(painter->device()->height()); + } + fillBackground(painter, bgRect, bg, origin, gradientRect); + } + if (fd->border != 0) { + painter->save(); + painter->setBrush(Qt::lightGray); + painter->setPen(Qt::NoPen); + + const qreal leftEdge = rect.left() + fd->leftMargin.toReal(); + const qreal border = fd->border.toReal(); + const qreal topMargin = fd->topMargin.toReal(); + const qreal leftMargin = fd->leftMargin.toReal(); + const qreal bottomMargin = fd->bottomMargin.toReal(); + const qreal rightMargin = fd->rightMargin.toReal(); + const qreal w = rect.width() - 2 * border - leftMargin - rightMargin; + const qreal h = rect.height() - 2 * border - topMargin - bottomMargin; + + drawBorder(painter, QRectF(leftEdge, rect.top() + topMargin, w + border, h + border), + fd->effectiveTopMargin.toReal(), fd->effectiveBottomMargin.toReal(), + border, frame->frameFormat().borderBrush(), frame->frameFormat().borderStyle()); + + painter->restore(); + } +} + +static void adjustContextSelectionsForCell(QAbstractTextDocumentLayout::PaintContext &cell_context, + const QTextTableCell &cell, + int r, int c, + const int *selectedTableCells) +{ + for (int i = 0; i < cell_context.selections.size(); ++i) { + int row_start = selectedTableCells[i * 4]; + int col_start = selectedTableCells[i * 4 + 1]; + int num_rows = selectedTableCells[i * 4 + 2]; + int num_cols = selectedTableCells[i * 4 + 3]; + + if (row_start != -1) { + if (r >= row_start && r < row_start + num_rows + && c >= col_start && c < col_start + num_cols) + { + int firstPosition = cell.firstPosition(); + int lastPosition = cell.lastPosition(); + + // make sure empty cells are still selected + if (firstPosition == lastPosition) + ++lastPosition; + + cell_context.selections[i].cursor.setPosition(firstPosition); + cell_context.selections[i].cursor.setPosition(lastPosition, QTextCursor::KeepAnchor); + } else { + cell_context.selections[i].cursor.clearSelection(); + } + } + + // FullWidthSelection is not useful for tables + cell_context.selections[i].format.clearProperty(QTextFormat::FullWidthSelection); + } +} + +void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *painter, + const QAbstractTextDocumentLayout::PaintContext &context, + QTextFrame *frame) const +{ + QTextFrameData *fd = data(frame); + // ####### + if (fd->layoutDirty) + return; + Q_ASSERT(!fd->sizeDirty); + Q_ASSERT(!fd->layoutDirty); + + const QPointF off = offset + fd->position.toPointF(); + if (context.clip.isValid() + && (off.y() > context.clip.bottom() || off.y() + fd->size.height.toReal() < context.clip.top() + || off.x() > context.clip.right() || off.x() + fd->size.width.toReal() < context.clip.left())) + return; + +// LDEBUG << debug_indent << "drawFrame" << frame->firstPosition() << "--" << frame->lastPosition() << "at" << offset; +// INC_INDENT; + + // if the cursor is /on/ a table border we may need to repaint it + // afterwards, as we usually draw the decoration first + QTextBlock cursorBlockNeedingRepaint; + QPointF offsetOfRepaintedCursorBlock = off; + + QTextTable *table = qobject_cast(frame); + const QRectF frameRect(off, fd->size.toSizeF()); + + if (table) { + const int rows = table->rows(); + const int columns = table->columns(); + QTextTableData *td = static_cast(data(table)); + + QVarLengthArray selectedTableCells(context.selections.size() * 4); + for (int i = 0; i < context.selections.size(); ++i) { + const QAbstractTextDocumentLayout::Selection &s = context.selections.at(i); + int row_start = -1, col_start = -1, num_rows = -1, num_cols = -1; + + if (s.cursor.currentTable() == table) + s.cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols); + + selectedTableCells[i * 4] = row_start; + selectedTableCells[i * 4 + 1] = col_start; + selectedTableCells[i * 4 + 2] = num_rows; + selectedTableCells[i * 4 + 3] = num_cols; + } + + QFixed pageHeight = QFixed::fromReal(document->pageSize().height()); + if (pageHeight <= 0) + pageHeight = QFIXED_MAX; + + const int tableStartPage = (td->position.y / pageHeight).truncate(); + const int tableEndPage = ((td->position.y + td->size.height) / pageHeight).truncate(); + + qreal border = td->border.toReal(); + drawFrameDecoration(painter, frame, fd, context.clip, frameRect); + + // draw the table headers + const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1); + int page = tableStartPage + 1; + while (page <= tableEndPage) { + const QFixed pageTop = page * pageHeight + td->effectiveTopMargin + td->cellSpacing + td->border; + const qreal headerOffset = (pageTop - td->rowPositions.at(0)).toReal(); + for (int r = 0; r < headerRowCount; ++r) { + for (int c = 0; c < columns; ++c) { + QTextTableCell cell = table->cellAt(r, c); + QAbstractTextDocumentLayout::PaintContext cell_context = context; + adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data()); + QRectF cellRect = td->cellRect(cell); + + cellRect.translate(off.x(), headerOffset); + // we need to account for the cell border in the clipping test + int leftAdjust = qMin(qreal(0), 1 - border); + if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip)) + continue; + + drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint, + &offsetOfRepaintedCursorBlock); + } + } + ++page; + } + + int firstRow = 0; + int lastRow = rows; + + if (context.clip.isValid()) { + QVector::ConstIterator rowIt = qLowerBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.top() - off.y())); + if (rowIt != td->rowPositions.constEnd() && rowIt != td->rowPositions.constBegin()) { + --rowIt; + firstRow = rowIt - td->rowPositions.constBegin(); + } + + rowIt = qUpperBound(td->rowPositions.constBegin(), td->rowPositions.constEnd(), QFixed::fromReal(context.clip.bottom() - off.y())); + if (rowIt != td->rowPositions.constEnd()) { + ++rowIt; + lastRow = rowIt - td->rowPositions.constBegin(); + } + } + + for (int c = 0; c < columns; ++c) { + QTextTableCell cell = table->cellAt(firstRow, c); + firstRow = qMin(firstRow, cell.row()); + } + + for (int r = firstRow; r < lastRow; ++r) { + for (int c = 0; c < columns; ++c) { + QTextTableCell cell = table->cellAt(r, c); + QAbstractTextDocumentLayout::PaintContext cell_context = context; + adjustContextSelectionsForCell(cell_context, cell, r, c, selectedTableCells.data()); + QRectF cellRect = td->cellRect(cell); + + cellRect.translate(off); + // we need to account for the cell border in the clipping test + int leftAdjust = qMin(qreal(0), 1 - border); + if (cell_context.clip.isValid() && !cellRect.adjusted(leftAdjust, leftAdjust, border, border).intersects(cell_context.clip)) + continue; + + drawTableCell(cellRect, painter, cell_context, table, td, r, c, &cursorBlockNeedingRepaint, + &offsetOfRepaintedCursorBlock); + } + } + + } else { + drawFrameDecoration(painter, frame, fd, context.clip, frameRect); + + QTextFrame::Iterator it = frame->begin(); + + if (frame == docPrivate->rootFrame()) + it = frameIteratorForYPosition(QFixed::fromReal(context.clip.top())); + + QList floats; + for (int i = 0; i < fd->floats.count(); ++i) + floats.append(fd->floats.at(i)); + + drawFlow(off, painter, context, it, floats, &cursorBlockNeedingRepaint); + } + + if (cursorBlockNeedingRepaint.isValid()) { + const QPen oldPen = painter->pen(); + painter->setPen(context.palette.color(QPalette::Text)); + const int cursorPos = context.cursorPosition - cursorBlockNeedingRepaint.position(); + cursorBlockNeedingRepaint.layout()->drawCursor(painter, offsetOfRepaintedCursorBlock, + cursorPos, cursorWidth); + painter->setPen(oldPen); + } + +// DEC_INDENT; + + return; +} + +void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &cell_context, + QTextTable *table, QTextTableData *td, int r, int c, + QTextBlock *cursorBlockNeedingRepaint, QPointF *cursorBlockOffset) const +{ + QTextTableCell cell = table->cellAt(r, c); + int rspan = cell.rowSpan(); + int cspan = cell.columnSpan(); + if (rspan != 1) { + int cr = cell.row(); + if (cr != r) + return; + } + if (cspan != 1) { + int cc = cell.column(); + if (cc != c) + return; + } + + QTextFormat fmt = cell.format(); + const QFixed leftPadding = td->leftPadding(fmt); + const QFixed topPadding = td->topPadding(fmt); + + if (td->border != 0) { + const QBrush oldBrush = painter->brush(); + const QPen oldPen = painter->pen(); + + const qreal border = td->border.toReal(); + + QRectF borderRect(cellRect.left() - border, cellRect.top() - border, cellRect.width() + border, cellRect.height() + border); + + // invert the border style for cells + QTextFrameFormat::BorderStyle cellBorder = table->format().borderStyle(); + switch (cellBorder) { + case QTextFrameFormat::BorderStyle_Inset: + cellBorder = QTextFrameFormat::BorderStyle_Outset; + break; + case QTextFrameFormat::BorderStyle_Outset: + cellBorder = QTextFrameFormat::BorderStyle_Inset; + break; + case QTextFrameFormat::BorderStyle_Groove: + cellBorder = QTextFrameFormat::BorderStyle_Ridge; + break; + case QTextFrameFormat::BorderStyle_Ridge: + cellBorder = QTextFrameFormat::BorderStyle_Groove; + break; + default: + break; + } + + qreal topMargin = (td->effectiveTopMargin + td->cellSpacing + td->border).toReal(); + qreal bottomMargin = (td->effectiveBottomMargin + td->cellSpacing + td->border).toReal(); + + const int headerRowCount = qMin(table->format().headerRowCount(), table->rows() - 1); + if (r >= headerRowCount) + topMargin += td->headerHeight.toReal(); + + drawBorder(painter, borderRect, topMargin, bottomMargin, + border, table->format().borderBrush(), cellBorder); + + painter->setBrush(oldBrush); + painter->setPen(oldPen); + } + + const QBrush bg = cell.format().background(); + const QPointF brushOrigin = painter->brushOrigin(); + if (bg.style() != Qt::NoBrush) { + fillBackground(painter, cellRect, bg, cellRect.topLeft()); + + if (bg.style() > Qt::SolidPattern) + painter->setBrushOrigin(cellRect.topLeft()); + } + + const QFixed verticalOffset = td->cellVerticalOffsets.at(c + r * table->columns()); + + const QPointF cellPos = QPointF(cellRect.left() + leftPadding.toReal(), + cellRect.top() + (topPadding + verticalOffset).toReal()); + + QTextBlock repaintBlock; + drawFlow(cellPos, painter, cell_context, cell.begin(), + td->childFrameMap.values(r + c * table->rows()), + &repaintBlock); + if (repaintBlock.isValid()) { + *cursorBlockNeedingRepaint = repaintBlock; + *cursorBlockOffset = cellPos; + } + + if (bg.style() > Qt::SolidPattern) + painter->setBrushOrigin(brushOrigin); +} + +void QTextDocumentLayoutPrivate::drawFlow(const QPointF &offset, QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context, + QTextFrame::Iterator it, const QList &floats, QTextBlock *cursorBlockNeedingRepaint) const +{ + Q_Q(const QTextDocumentLayout); + const bool inRootFrame = (!it.atEnd() && it.parentFrame() && it.parentFrame()->parentFrame() == 0); + + QVector::ConstIterator lastVisibleCheckPoint = checkPoints.end(); + if (inRootFrame && context.clip.isValid()) { + lastVisibleCheckPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), QFixed::fromReal(context.clip.bottom())); + } + + QTextBlock previousBlock; + QTextFrame *previousFrame = 0; + + for (; !it.atEnd(); ++it) { + QTextFrame *c = it.currentFrame(); + + if (inRootFrame && !checkPoints.isEmpty()) { + int currentPosInDoc; + if (c) + currentPosInDoc = c->firstPosition(); + else + currentPosInDoc = it.currentBlock().position(); + + // if we're past what is already laid out then we're better off + // not trying to draw things that may not be positioned correctly yet + if (currentPosInDoc >= checkPoints.last().positionInFrame) + break; + + if (lastVisibleCheckPoint != checkPoints.end() + && context.clip.isValid() + && currentPosInDoc >= lastVisibleCheckPoint->positionInFrame + ) + break; + } + + if (c) + drawFrame(offset, painter, context, c); + else { + QAbstractTextDocumentLayout::PaintContext pc = context; + if (isEmptyBlockAfterTable(it.currentBlock(), previousFrame)) + pc.selections.clear(); + drawBlock(offset, painter, pc, it.currentBlock(), inRootFrame); + } + + // when entering a table and the previous block is empty + // then layoutFlow 'hides' the block that just causes a + // new line by positioning it /on/ the table border. as we + // draw that block before the table itself the decoration + // 'overpaints' the cursor and we need to paint it afterwards + // again + if (isEmptyBlockBeforeTable(previousBlock, previousBlock.blockFormat(), it) + && previousBlock.contains(context.cursorPosition) + ) { + *cursorBlockNeedingRepaint = previousBlock; + } + + previousBlock = it.currentBlock(); + previousFrame = c; + } + + for (int i = 0; i < floats.count(); ++i) { + QTextFrame *frame = floats.at(i); + if (!isFrameFromInlineObject(frame) + || frame->frameFormat().position() == QTextFrameFormat::InFlow) + continue; + + const int pos = frame->firstPosition() - 1; + QTextCharFormat format = const_cast(q)->format(pos); + QTextObjectInterface *handler = q->handlerForObject(format.objectType()); + if (handler) { + QRectF rect = frameBoundingRectInternal(frame); + handler->drawObject(painter, rect, document, pos, format); + } + } +} + +void QTextDocumentLayoutPrivate::drawBlock(const QPointF &offset, QPainter *painter, + const QAbstractTextDocumentLayout::PaintContext &context, + QTextBlock bl, bool inRootFrame) const +{ + const QTextLayout *tl = bl.layout(); + QRectF r = tl->boundingRect(); + r.translate(offset + tl->position()); + if (context.clip.isValid() && (r.bottom() < context.clip.y() || r.top() > context.clip.bottom())) + return; +// LDEBUG << debug_indent << "drawBlock" << bl.position() << "at" << offset << "br" << tl->boundingRect(); + + QTextBlockFormat blockFormat = bl.blockFormat(); + + QBrush bg = blockFormat.background(); + if (bg != Qt::NoBrush) { + QRectF rect = r; + + // extend the background rectangle if we're in the root frame with NoWrap, + // as the rect of the text block will then be only the width of the text + // instead of the full page width + if (inRootFrame && document->pageSize().width() <= 0) { + const QTextFrameData *fd = data(document->rootFrame()); + rect.setRight((fd->size.width - fd->rightMargin).toReal()); + } + + fillBackground(painter, rect, bg, r.topLeft()); + } + + QVector selections; + int blpos = bl.position(); + int bllen = bl.length(); + const QTextCharFormat *selFormat = 0; + for (int i = 0; i < context.selections.size(); ++i) { + const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i); + const int selStart = range.cursor.selectionStart() - blpos; + const int selEnd = range.cursor.selectionEnd() - blpos; + if (selStart < bllen && selEnd > 0 + && selEnd > selStart) { + QTextLayout::FormatRange o; + o.start = selStart; + o.length = selEnd - selStart; + o.format = range.format; + selections.append(o); + } else if (! range.cursor.hasSelection() && range.format.hasProperty(QTextFormat::FullWidthSelection) + && bl.contains(range.cursor.position())) { + // for full width selections we don't require an actual selection, just + // a position to specify the line. that's more convenience in usage. + QTextLayout::FormatRange o; + QTextLine l = tl->lineForTextPosition(range.cursor.position() - blpos); + o.start = l.textStart(); + o.length = l.textLength(); + if (o.start + o.length == bllen - 1) + ++o.length; // include newline + o.format = range.format; + selections.append(o); + } + if (selStart < 0 && selEnd >= 1) + selFormat = &range.format; + } + + QTextObject *object = document->objectForFormat(bl.blockFormat()); + if (object && object->format().toListFormat().style() != QTextListFormat::ListStyleUndefined) + drawListItem(offset, painter, context, bl, selFormat); + + QPen oldPen = painter->pen(); + painter->setPen(context.palette.color(QPalette::Text)); + + tl->draw(painter, offset, selections, context.clip.isValid() ? (context.clip & clipRect) : clipRect); + + if ((context.cursorPosition >= blpos && context.cursorPosition < blpos + bllen) + || (context.cursorPosition < -1 && !tl->preeditAreaText().isEmpty())) { + int cpos = context.cursorPosition; + if (cpos < -1) + cpos = tl->preeditAreaPosition() - (cpos + 2); + else + cpos -= blpos; + tl->drawCursor(painter, offset, cpos, cursorWidth); + } + + if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) { + const qreal width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth).value(r.width()); + painter->setPen(context.palette.color(QPalette::Dark)); + qreal y = r.bottom(); + if (bl.length() == 1) + y = r.top() + r.height() / 2; + + const qreal middleX = r.left() + r.width() / 2; + painter->drawLine(QLineF(middleX - width / 2, y, middleX + width / 2, y)); + } + + painter->setPen(oldPen); +} + + +void QTextDocumentLayoutPrivate::drawListItem(const QPointF &offset, QPainter *painter, + const QAbstractTextDocumentLayout::PaintContext &context, + QTextBlock bl, const QTextCharFormat *selectionFormat) const +{ + Q_Q(const QTextDocumentLayout); + const QTextBlockFormat blockFormat = bl.blockFormat(); + const QTextCharFormat charFormat = QTextCursor(bl).charFormat(); + QFont font(charFormat.font()); + if (q->paintDevice()) + font = QFont(font, q->paintDevice()); + + const QFontMetrics fontMetrics(font); + QTextObject * const object = document->objectForFormat(blockFormat); + const QTextListFormat lf = object->format().toListFormat(); + int style = lf.style(); + QString itemText; + QSizeF size; + + if (blockFormat.hasProperty(QTextFormat::ListStyle)) + style = QTextListFormat::Style(blockFormat.intProperty(QTextFormat::ListStyle)); + + QTextLayout *layout = bl.layout(); + if (layout->lineCount() == 0) + return; + QTextLine firstLine = layout->lineAt(0); + Q_ASSERT(firstLine.isValid()); + QPointF pos = (offset + layout->position()).toPoint(); + Qt::LayoutDirection dir = bl.textDirection(); + { + QRectF textRect = firstLine.naturalTextRect(); + pos += textRect.topLeft().toPoint(); + if (dir == Qt::RightToLeft) + pos.rx() += textRect.width(); + } + + switch (style) { + case QTextListFormat::ListDecimal: + case QTextListFormat::ListLowerAlpha: + case QTextListFormat::ListUpperAlpha: + case QTextListFormat::ListLowerRoman: + case QTextListFormat::ListUpperRoman: + itemText = static_cast(object)->itemText(bl); + size.setWidth(fontMetrics.width(itemText)); + size.setHeight(fontMetrics.height()); + break; + + case QTextListFormat::ListSquare: + case QTextListFormat::ListCircle: + case QTextListFormat::ListDisc: + size.setWidth(fontMetrics.lineSpacing() / 3); + size.setHeight(size.width()); + break; + + case QTextListFormat::ListStyleUndefined: + return; + default: return; + } + + QRectF r(pos, size); + + qreal xoff = fontMetrics.width(QLatin1Char(' ')); + if (dir == Qt::LeftToRight) + xoff = -xoff - size.width(); + r.translate( xoff, (fontMetrics.height() / 2 - size.height() / 2)); + + painter->save(); + + painter->setRenderHint(QPainter::Antialiasing); + + if (selectionFormat) { + painter->setPen(QPen(selectionFormat->foreground(), 0)); + painter->fillRect(r, selectionFormat->background()); + } else { + QBrush fg = charFormat.foreground(); + if (fg == Qt::NoBrush) + fg = context.palette.text(); + painter->setPen(QPen(fg, 0)); + } + + QBrush brush = context.palette.brush(QPalette::Text); + + switch (style) { + case QTextListFormat::ListDecimal: + case QTextListFormat::ListLowerAlpha: + case QTextListFormat::ListUpperAlpha: + case QTextListFormat::ListLowerRoman: + case QTextListFormat::ListUpperRoman: { + QTextLayout layout(itemText, font, q->paintDevice()); + layout.setCacheEnabled(true); + QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute); + option.setTextDirection(dir); + layout.setTextOption(option); + layout.beginLayout(); + QTextLine line = layout.createLine(); + if (line.isValid()) + line.setLeadingIncluded(true); + layout.endLayout(); + layout.draw(painter, QPointF(r.left(), pos.y())); + break; + } + case QTextListFormat::ListSquare: + painter->fillRect(r, brush); + break; + case QTextListFormat::ListCircle: + painter->setPen(QPen(brush, 0)); + painter->drawEllipse(r.translated(0.5, 0.5)); // pixel align for sharper rendering + break; + case QTextListFormat::ListDisc: + painter->setBrush(brush); + painter->setPen(Qt::NoPen); + painter->drawEllipse(r); + break; + case QTextListFormat::ListStyleUndefined: + break; + default: + break; + } + + painter->restore(); +} + +static QFixed flowPosition(const QTextFrame::iterator it) +{ + if (it.atEnd()) + return 0; + + if (it.currentFrame()) { + return data(it.currentFrame())->position.y; + } else { + QTextBlock block = it.currentBlock(); + QTextLayout *layout = block.layout(); + if (layout->lineCount() == 0) + return QFixed::fromReal(layout->position().y()); + else + return QFixed::fromReal(layout->position().y() + layout->lineAt(0).y()); + } +} + +static QFixed firstChildPos(const QTextFrame *f) +{ + return flowPosition(f->begin()); +} + +QTextLayoutStruct QTextDocumentLayoutPrivate::layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width, + int layoutFrom, int layoutTo, QTextTableData *td, + QFixed absoluteTableY, bool withPageBreaks) +{ + LDEBUG << "layoutCell"; + QTextLayoutStruct layoutStruct; + layoutStruct.frame = t; + layoutStruct.minimumWidth = 0; + layoutStruct.maximumWidth = QFIXED_MAX; + layoutStruct.y = 0; + + const QTextFormat fmt = cell.format(); + const QFixed topPadding = td->topPadding(fmt); + if (withPageBreaks) { + layoutStruct.frameY = absoluteTableY + td->rowPositions.at(cell.row()) + topPadding; + } + layoutStruct.x_left = 0; + layoutStruct.x_right = width; + // we get called with different widths all the time (for example for figuring + // out the min/max widths), so we always have to do the full layout ;( + // also when for example in a table layoutFrom/layoutTo affect only one cell, + // making that one cell grow the available width of the other cells may change + // (shrink) and therefore when layoutCell gets called for them they have to + // be re-laid out, even if layoutFrom/layoutTo is not in their range. Hence + // this line: + + layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height()); + if (layoutStruct.pageHeight < 0 || !withPageBreaks) + layoutStruct.pageHeight = QFIXED_MAX; + const int currentPage = layoutStruct.currentPage(); + layoutStruct.pageTopMargin = td->effectiveTopMargin + td->cellSpacing + td->border + topPadding; + layoutStruct.pageBottomMargin = td->effectiveBottomMargin + td->cellSpacing + td->border + td->bottomPadding(fmt); + layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin; + + layoutStruct.fullLayout = true; + + QFixed pageTop = currentPage * layoutStruct.pageHeight + layoutStruct.pageTopMargin - layoutStruct.frameY; + layoutStruct.y = qMax(layoutStruct.y, pageTop); + + const QList childFrames = td->childFrameMap.values(cell.row() + cell.column() * t->rows()); + for (int i = 0; i < childFrames.size(); ++i) { + QTextFrame *frame = childFrames.at(i); + QTextFrameData *cd = data(frame); + cd->sizeDirty = true; + } + + layoutFlow(cell.begin(), &layoutStruct, layoutFrom, layoutTo, width); + + QFixed floatMinWidth; + + // floats that are located inside the text (like inline images) aren't taken into account by + // layoutFlow with regards to the cell height (layoutStruct->y), so for a safety measure we + // do that here. For example with + // when the image happens to be higher than the text + for (int i = 0; i < childFrames.size(); ++i) { + QTextFrame *frame = childFrames.at(i); + QTextFrameData *cd = data(frame); + + if (frame->frameFormat().position() != QTextFrameFormat::InFlow) + layoutStruct.y = qMax(layoutStruct.y, cd->position.y + cd->size.height); + + floatMinWidth = qMax(floatMinWidth, cd->minimumWidth); + } + + // constraint the maximumWidth by the minimum width of the fixed size floats, to + // keep them visible + layoutStruct.maximumWidth = qMax(layoutStruct.maximumWidth, floatMinWidth); + + // as floats in cells get added to the table's float list but must not affect + // floats in other cells we must clear the list here. + data(t)->floats.clear(); + +// qDebug() << "layoutCell done"; + + return layoutStruct; +} + +QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom, int layoutTo, QFixed parentY) +{ + LDEBUG << "layoutTable"; + QTextTableData *td = static_cast(data(table)); + Q_ASSERT(td->sizeDirty); + const int rows = table->rows(); + const int columns = table->columns(); + + const QTextTableFormat fmt = table->format(); + + td->childFrameMap.clear(); + { + const QList children = table->childFrames(); + for (int i = 0; i < children.count(); ++i) { + QTextFrame *frame = children.at(i); + QTextTableCell cell = table->cellAt(frame->firstPosition()); + td->childFrameMap.insertMulti(cell.row() + cell.column() * rows, frame); + } + } + + QVector columnWidthConstraints = fmt.columnWidthConstraints(); + if (columnWidthConstraints.size() != columns) + columnWidthConstraints.resize(columns); + Q_ASSERT(columnWidthConstraints.count() == columns); + + const QFixed cellSpacing = td->cellSpacing = QFixed::fromReal(scaleToDevice(fmt.cellSpacing())); + td->deviceScale = scaleToDevice(qreal(1)); + td->cellPadding = QFixed::fromReal(scaleToDevice(fmt.cellPadding())); + const QFixed leftMargin = td->leftMargin + td->border + td->padding; + const QFixed rightMargin = td->rightMargin + td->border + td->padding; + const QFixed topMargin = td->topMargin + td->border + td->padding; + + const QFixed absoluteTableY = parentY + td->position.y; + + const QTextOption::WrapMode oldDefaultWrapMode = docPrivate->defaultTextOption.wrapMode(); + +recalc_minmax_widths: + + QFixed remainingWidth = td->contentsWidth; + // two (vertical) borders per cell per column + remainingWidth -= columns * 2 * td->border; + // inter-cell spacing + remainingWidth -= (columns - 1) * cellSpacing; + // cell spacing at the left and right hand side + remainingWidth -= 2 * cellSpacing; + // remember the width used to distribute to percentaged columns + const QFixed initialTotalWidth = remainingWidth; + + td->widths.resize(columns); + td->widths.fill(0); + + td->minWidths.resize(columns); + // start with a minimum width of 0. totally empty + // cells of default created tables are invisible otherwise + // and therefore hardly editable + td->minWidths.fill(1); + + td->maxWidths.resize(columns); + td->maxWidths.fill(QFIXED_MAX); + + // calculate minimum and maximum sizes of the columns + for (int i = 0; i < columns; ++i) { + for (int row = 0; row < rows; ++row) { + const QTextTableCell cell = table->cellAt(row, i); + const int cspan = cell.columnSpan(); + + if (cspan > 1 && i != cell.column()) + continue; + + const QTextFormat fmt = cell.format(); + const QFixed leftPadding = td->leftPadding(fmt); + const QFixed rightPadding = td->rightPadding(fmt); + const QFixed widthPadding = leftPadding + rightPadding; + + // to figure out the min and the max width lay out the cell at + // maximum width. otherwise the maxwidth calculation sometimes + // returns wrong values + QTextLayoutStruct layoutStruct = layoutCell(table, cell, QFIXED_MAX, layoutFrom, + layoutTo, td, absoluteTableY, + /*withPageBreaks =*/false); + + // distribute the minimum width over all columns the cell spans + QFixed widthToDistribute = layoutStruct.minimumWidth + widthPadding; + for (int n = 0; n < cspan; ++n) { + const int col = i + n; + QFixed w = widthToDistribute / (cspan - n); + td->minWidths[col] = qMax(td->minWidths.at(col), w); + widthToDistribute -= td->minWidths.at(col); + if (widthToDistribute <= 0) + break; + } + + QFixed maxW = td->maxWidths.at(i); + if (layoutStruct.maximumWidth != QFIXED_MAX) { + if (maxW == QFIXED_MAX) + maxW = layoutStruct.maximumWidth + widthPadding; + else + maxW = qMax(maxW, layoutStruct.maximumWidth + widthPadding); + } + if (maxW == QFIXED_MAX) + continue; + + widthToDistribute = maxW; + for (int n = 0; n < cspan; ++n) { + const int col = i + n; + QFixed w = widthToDistribute / (cspan - n); + td->maxWidths[col] = qMax(td->minWidths.at(col), w); + widthToDistribute -= td->maxWidths.at(col); + if (widthToDistribute <= 0) + break; + } + } + } + + // set fixed values, figure out total percentages used and number of + // variable length cells. Also assign the minimum width for variable columns. + QFixed totalPercentage; + int variableCols = 0; + QFixed totalMinWidth = 0; + for (int i = 0; i < columns; ++i) { + const QTextLength &length = columnWidthConstraints.at(i); + if (length.type() == QTextLength::FixedLength) { + td->minWidths[i] = td->widths[i] = qMax(scaleToDevice(QFixed::fromReal(length.rawValue())), td->minWidths.at(i)); + remainingWidth -= td->widths.at(i); + } else if (length.type() == QTextLength::PercentageLength) { + totalPercentage += QFixed::fromReal(length.rawValue()); + } else if (length.type() == QTextLength::VariableLength) { + variableCols++; + + td->widths[i] = td->minWidths.at(i); + remainingWidth -= td->minWidths.at(i); + } + totalMinWidth += td->minWidths.at(i); + } + + // set percentage values + { + const QFixed totalPercentagedWidth = initialTotalWidth * totalPercentage / 100; + QFixed remainingMinWidths = totalMinWidth; + for (int i = 0; i < columns; ++i) { + remainingMinWidths -= td->minWidths.at(i); + if (columnWidthConstraints.at(i).type() == QTextLength::PercentageLength) { + const QFixed allottedPercentage = QFixed::fromReal(columnWidthConstraints.at(i).rawValue()); + + const QFixed percentWidth = totalPercentagedWidth * allottedPercentage / totalPercentage; + if (percentWidth >= td->minWidths.at(i)) { + td->widths[i] = qBound(td->minWidths.at(i), percentWidth, remainingWidth - remainingMinWidths); + } else { + td->widths[i] = td->minWidths.at(i); + } + remainingWidth -= td->widths.at(i); + } + } + } + + // for variable columns distribute the remaining space + if (variableCols > 0 && remainingWidth > 0) { + QVarLengthArray columnsWithProperMaxSize; + for (int i = 0; i < columns; ++i) + if (columnWidthConstraints.at(i).type() == QTextLength::VariableLength + && td->maxWidths.at(i) != QFIXED_MAX) + columnsWithProperMaxSize.append(i); + + QFixed lastRemainingWidth = remainingWidth; + while (remainingWidth > 0) { + for (int k = 0; k < columnsWithProperMaxSize.count(); ++k) { + const int col = columnsWithProperMaxSize[k]; + const int colsLeft = columnsWithProperMaxSize.count() - k; + const QFixed w = qMin(td->maxWidths.at(col) - td->widths.at(col), remainingWidth / colsLeft); + td->widths[col] += w; + remainingWidth -= w; + } + if (remainingWidth == lastRemainingWidth) + break; + lastRemainingWidth = remainingWidth; + } + + if (remainingWidth > 0 + // don't unnecessarily grow variable length sized tables + && fmt.width().type() != QTextLength::VariableLength) { + const QFixed widthPerAnySizedCol = remainingWidth / variableCols; + for (int col = 0; col < columns; ++col) { + if (columnWidthConstraints.at(col).type() == QTextLength::VariableLength) + td->widths[col] += widthPerAnySizedCol; + } + } + } + + td->columnPositions.resize(columns); + td->columnPositions[0] = leftMargin /*includes table border*/ + cellSpacing + td->border; + + for (int i = 1; i < columns; ++i) + td->columnPositions[i] = td->columnPositions.at(i-1) + td->widths.at(i-1) + 2 * td->border + cellSpacing; + + // - margin to compensate the + margin in columnPositions[0] + const QFixed contentsWidth = td->columnPositions.last() + td->widths.last() + td->padding + td->border + cellSpacing - leftMargin; + + // if the table is too big and causes an overflow re-do the layout with WrapAnywhere as wrap + // mode + if (docPrivate->defaultTextOption.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere + && contentsWidth > td->contentsWidth) { + docPrivate->defaultTextOption.setWrapMode(QTextOption::WrapAnywhere); + // go back to the top of the function + goto recalc_minmax_widths; + } + + td->contentsWidth = contentsWidth; + + docPrivate->defaultTextOption.setWrapMode(oldDefaultWrapMode); + + td->heights.resize(rows); + td->heights.fill(0); + + td->rowPositions.resize(rows); + td->rowPositions[0] = topMargin /*includes table border*/ + cellSpacing + td->border; + + bool haveRowSpannedCells = false; + + // need to keep track of cell heights for vertical alignment + QVector cellHeights; + cellHeights.reserve(rows * columns); + + QFixed pageHeight = QFixed::fromReal(document->pageSize().height()); + if (pageHeight <= 0) + pageHeight = QFIXED_MAX; + + QVector heightToDistribute; + heightToDistribute.resize(columns); + + td->headerHeight = 0; + const int headerRowCount = qMin(table->format().headerRowCount(), rows - 1); + const QFixed originalTopMargin = td->effectiveTopMargin; + bool hasDroppedTable = false; + + // now that we have the column widths we can lay out all cells with the right width. + // spanning cells are only allowed to grow the last row spanned by the cell. + // + // ### this could be made faster by iterating over the cells array of QTextTable + for (int r = 0; r < rows; ++r) { + td->calcRowPosition(r); + + const int tableStartPage = (absoluteTableY / pageHeight).truncate(); + const int currentPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate(); + const QFixed pageBottom = (currentPage + 1) * pageHeight - td->effectiveBottomMargin - absoluteTableY - cellSpacing - td->border; + const QFixed pageTop = currentPage * pageHeight + td->effectiveTopMargin - absoluteTableY + cellSpacing + td->border; + const QFixed nextPageTop = pageTop + pageHeight; + + if (td->rowPositions[r] > pageBottom) + td->rowPositions[r] = nextPageTop; + else if (td->rowPositions[r] < pageTop) + td->rowPositions[r] = pageTop; + + bool dropRowToNextPage = true; + int cellCountBeforeRow = cellHeights.size(); + + // if we drop the row to the next page we need to subtract the drop + // distance from any row spanning cells + QFixed dropDistance = 0; + +relayout: + const int rowStartPage = ((td->rowPositions[r] + absoluteTableY) / pageHeight).truncate(); + // if any of the header rows or the first non-header row start on the next page + // then the entire header should be dropped + if (r <= headerRowCount && rowStartPage > tableStartPage && !hasDroppedTable) { + td->rowPositions[0] = nextPageTop; + cellHeights.clear(); + td->effectiveTopMargin = originalTopMargin; + hasDroppedTable = true; + r = -1; + continue; + } + + int rowCellCount = 0; + for (int c = 0; c < columns; ++c) { + QTextTableCell cell = table->cellAt(r, c); + const int rspan = cell.rowSpan(); + const int cspan = cell.columnSpan(); + + if (cspan > 1 && cell.column() != c) + continue; + + if (rspan > 1) { + haveRowSpannedCells = true; + + const int cellRow = cell.row(); + if (cellRow != r) { + // the last row gets all the remaining space + if (cellRow + rspan - 1 == r) + td->heights[r] = qMax(td->heights.at(r), heightToDistribute.at(c) - dropDistance); + continue; + } + } + + const QTextFormat fmt = cell.format(); + + const QFixed topPadding = td->topPadding(fmt); + const QFixed bottomPadding = td->bottomPadding(fmt); + const QFixed leftPadding = td->leftPadding(fmt); + const QFixed rightPadding = td->rightPadding(fmt); + const QFixed widthPadding = leftPadding + rightPadding; + + ++rowCellCount; + + const QFixed width = td->cellWidth(c, cspan) - widthPadding; + QTextLayoutStruct layoutStruct = layoutCell(table, cell, width, + layoutFrom, layoutTo, + td, absoluteTableY, + /*withPageBreaks =*/true); + + const QFixed height = layoutStruct.y + bottomPadding + topPadding; + + if (rspan > 1) + heightToDistribute[c] = height + dropDistance; + else + td->heights[r] = qMax(td->heights.at(r), height); + + cellHeights.append(layoutStruct.y); + + QFixed childPos = td->rowPositions.at(r) + topPadding + flowPosition(cell.begin()); + if (childPos < pageBottom) + dropRowToNextPage = false; + } + + if (rowCellCount > 0 && dropRowToNextPage) { + dropDistance = nextPageTop - td->rowPositions[r]; + td->rowPositions[r] = nextPageTop; + td->heights[r] = 0; + dropRowToNextPage = false; + cellHeights.resize(cellCountBeforeRow); + if (r > headerRowCount) + td->heights[r-1] = pageBottom - td->rowPositions[r-1]; + goto relayout; + } + + if (haveRowSpannedCells) { + const QFixed effectiveHeight = td->heights.at(r) + td->border + cellSpacing + td->border; + for (int c = 0; c < columns; ++c) + heightToDistribute[c] = qMax(heightToDistribute.at(c) - effectiveHeight - dropDistance, QFixed(0)); + } + + if (r == headerRowCount - 1) { + td->headerHeight = td->rowPositions[r] + td->heights[r] - td->rowPositions[0] + td->cellSpacing + 2 * td->border; + td->headerHeight -= td->headerHeight * (td->headerHeight / pageHeight).truncate(); + td->effectiveTopMargin += td->headerHeight; + } + } + + td->effectiveTopMargin = originalTopMargin; + + // now that all cells have been properly laid out, we can compute the + // vertical offsets for vertical alignment + td->cellVerticalOffsets.resize(rows * columns); + int cellIndex = 0; + for (int r = 0; r < rows; ++r) { + for (int c = 0; c < columns; ++c) { + QTextTableCell cell = table->cellAt(r, c); + if (cell.row() != r || cell.column() != c) + continue; + + const int rowSpan = cell.rowSpan(); + const QFixed availableHeight = td->rowPositions.at(r + rowSpan - 1) + td->heights.at(r + rowSpan - 1) - td->rowPositions.at(r); + + const QTextCharFormat cellFormat = cell.format(); + const QFixed cellHeight = cellHeights.at(cellIndex++) + td->topPadding(cellFormat) + td->bottomPadding(cellFormat); + + QFixed offset = 0; + switch (cellFormat.verticalAlignment()) { + case QTextCharFormat::AlignMiddle: + offset = (availableHeight - cellHeight) / 2; + break; + case QTextCharFormat::AlignBottom: + offset = availableHeight - cellHeight; + break; + default: + break; + }; + + for (int rd = 0; rd < cell.rowSpan(); ++rd) { + for (int cd = 0; cd < cell.columnSpan(); ++cd) { + const int index = (c + cd) + (r + rd) * columns; + td->cellVerticalOffsets[index] = offset; + } + } + } + } + + td->minimumWidth = td->columnPositions.at(0); + for (int i = 0; i < columns; ++i) { + td->minimumWidth += td->minWidths.at(i) + 2 * td->border + cellSpacing; + } + td->minimumWidth += rightMargin - td->border; + + td->maximumWidth = td->columnPositions.at(0); + for (int i = 0; i < columns; ++i) + if (td->maxWidths.at(i) != QFIXED_MAX) + td->maximumWidth += td->maxWidths.at(i) + 2 * td->border + cellSpacing; + td->maximumWidth += rightMargin - td->border; + + td->updateTableSize(); + td->sizeDirty = false; + return QRectF(); // invalid rect -> update everything +} + +void QTextDocumentLayoutPrivate::positionFloat(QTextFrame *frame, QTextLine *currentLine) +{ + QTextFrameData *fd = data(frame); + + QTextFrame *parent = frame->parentFrame(); + Q_ASSERT(parent); + QTextFrameData *pd = data(parent); + Q_ASSERT(pd && pd->currentLayoutStruct); + + QTextLayoutStruct *layoutStruct = pd->currentLayoutStruct; + + if (!pd->floats.contains(frame)) + pd->floats.append(frame); + fd->layoutDirty = true; + Q_ASSERT(!fd->sizeDirty); + +// qDebug() << "positionFloat:" << frame << "width=" << fd->size.width; + QFixed y = layoutStruct->y; + if (currentLine) { + QFixed left, right; + floatMargins(y, layoutStruct, &left, &right); +// qDebug() << "have line: right=" << right << "left=" << left << "textWidth=" << currentLine->width(); + if (right - left < QFixed::fromReal(currentLine->naturalTextWidth()) + fd->size.width) { + layoutStruct->pendingFloats.append(frame); +// qDebug() << " adding to pending list"; + return; + } + } + + bool frameSpansIntoNextPage = (y + layoutStruct->frameY + fd->size.height > layoutStruct->pageBottom); + if (frameSpansIntoNextPage && fd->size.height <= layoutStruct->pageHeight) { + layoutStruct->newPage(); + y = layoutStruct->y; + + frameSpansIntoNextPage = false; + } + + y = findY(y, layoutStruct, fd->size.width); + + QFixed left, right; + floatMargins(y, layoutStruct, &left, &right); + + if (frame->frameFormat().position() == QTextFrameFormat::FloatLeft) { + fd->position.x = left; + fd->position.y = y; + } else { + fd->position.x = right - fd->size.width; + fd->position.y = y; + } + + layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, fd->minimumWidth); + layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, fd->maximumWidth); + +// qDebug()<< "float positioned at " << fd->position.x << fd->position.y; + fd->layoutDirty = false; + + // If the frame is a table, then positioning it will affect the size if it covers more than + // one page, because of page breaks and repeating the header. + if (qobject_cast(frame) != 0) + fd->sizeDirty = frameSpansIntoNextPage; +} + +QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed parentY) +{ + LDEBUG << "layoutFrame (pre)"; + Q_ASSERT(data(f)->sizeDirty); +// qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame()); + + QTextFrameFormat fformat = f->frameFormat(); + + QTextFrame *parent = f->parentFrame(); + const QTextFrameData *pd = parent ? data(parent) : 0; + + const qreal maximumWidth = qMax(qreal(0), pd ? pd->contentsWidth.toReal() : document->pageSize().width()); + QFixed width = QFixed::fromReal(fformat.width().value(maximumWidth)); + if (fformat.width().type() == QTextLength::FixedLength) + width = scaleToDevice(width); + + const QFixed maximumHeight = pd ? pd->contentsHeight : -1; + const QFixed height = (maximumHeight != -1 || fformat.height().type() != QTextLength::PercentageLength) + ? QFixed::fromReal(fformat.height().value(maximumHeight.toReal())) + : -1; + + return layoutFrame(f, layoutFrom, layoutTo, width, height, parentY); +} + +QRectF QTextDocumentLayoutPrivate::layoutFrame(QTextFrame *f, int layoutFrom, int layoutTo, QFixed frameWidth, QFixed frameHeight, QFixed parentY) +{ + LDEBUG << "layoutFrame from=" << layoutFrom << "to=" << layoutTo; + Q_ASSERT(data(f)->sizeDirty); +// qDebug("layouting frame (%d--%d), parent=%p", f->firstPosition(), f->lastPosition(), f->parentFrame()); + + QTextFrameData *fd = data(f); + QFixed newContentsWidth; + + { + QTextFrameFormat fformat = f->frameFormat(); + // set sizes of this frame from the format + fd->topMargin = QFixed::fromReal(fformat.topMargin()); + fd->bottomMargin = QFixed::fromReal(fformat.bottomMargin()); + fd->leftMargin = QFixed::fromReal(fformat.leftMargin()); + fd->rightMargin = QFixed::fromReal(fformat.rightMargin()); + fd->border = QFixed::fromReal(fformat.border()); + fd->padding = QFixed::fromReal(fformat.padding()); + + QTextFrame *parent = f->parentFrame(); + const QTextFrameData *pd = parent ? data(parent) : 0; + + // accumulate top and bottom margins + if (parent) { + fd->effectiveTopMargin = pd->effectiveTopMargin + fd->topMargin + fd->border + fd->padding; + fd->effectiveBottomMargin = pd->effectiveBottomMargin + fd->topMargin + fd->border + fd->padding; + + if (qobject_cast(parent)) { + const QTextTableData *td = static_cast(pd); + fd->effectiveTopMargin += td->cellSpacing + td->border + td->cellPadding; + fd->effectiveBottomMargin += td->cellSpacing + td->border + td->cellPadding; + } + } else { + fd->effectiveTopMargin = fd->topMargin + fd->border + fd->padding; + fd->effectiveBottomMargin = fd->bottomMargin + fd->border + fd->padding; + } + + newContentsWidth = frameWidth - 2*(fd->border + fd->padding) + - fd->leftMargin - fd->rightMargin; + + if (frameHeight != -1) { + fd->contentsHeight = frameHeight - 2*(fd->border + fd->padding) + - fd->topMargin - fd->bottomMargin; + } else { + fd->contentsHeight = frameHeight; + } + } + + if (isFrameFromInlineObject(f)) { + // never reached, handled in resizeInlineObject/positionFloat instead + return QRectF(); + } + + if (QTextTable *table = qobject_cast(f)) { + fd->contentsWidth = newContentsWidth; + return layoutTable(table, layoutFrom, layoutTo, parentY); + } + + // set fd->contentsWidth temporarily, so that layoutFrame for the children + // picks the right width. We'll initialize it properly at the end of this + // function. + fd->contentsWidth = newContentsWidth; + + QTextLayoutStruct layoutStruct; + layoutStruct.frame = f; + layoutStruct.x_left = fd->leftMargin + fd->border + fd->padding; + layoutStruct.x_right = layoutStruct.x_left + newContentsWidth; + layoutStruct.y = fd->topMargin + fd->border + fd->padding; + layoutStruct.frameY = parentY + fd->position.y; + layoutStruct.contentsWidth = 0; + layoutStruct.minimumWidth = 0; + layoutStruct.maximumWidth = QFIXED_MAX; + layoutStruct.fullLayout = fd->oldContentsWidth != newContentsWidth; + layoutStruct.updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX))); + LDEBUG << "layoutStruct: x_left" << layoutStruct.x_left << "x_right" << layoutStruct.x_right + << "fullLayout" << layoutStruct.fullLayout; + fd->oldContentsWidth = newContentsWidth; + + layoutStruct.pageHeight = QFixed::fromReal(document->pageSize().height()); + if (layoutStruct.pageHeight < 0) + layoutStruct.pageHeight = QFIXED_MAX; + + const int currentPage = layoutStruct.pageHeight == 0 ? 0 : (layoutStruct.frameY / layoutStruct.pageHeight).truncate(); + layoutStruct.pageTopMargin = fd->effectiveTopMargin; + layoutStruct.pageBottomMargin = fd->effectiveBottomMargin; + layoutStruct.pageBottom = (currentPage + 1) * layoutStruct.pageHeight - layoutStruct.pageBottomMargin; + + if (!f->parentFrame()) + idealWidth = 0; // reset + + QTextFrame::Iterator it = f->begin(); + layoutFlow(it, &layoutStruct, layoutFrom, layoutTo); + + QFixed maxChildFrameWidth = 0; + QList children = f->childFrames(); + for (int i = 0; i < children.size(); ++i) { + QTextFrame *c = children.at(i); + QTextFrameData *cd = data(c); + maxChildFrameWidth = qMax(maxChildFrameWidth, cd->size.width); + } + + const QFixed marginWidth = 2*(fd->border + fd->padding) + fd->leftMargin + fd->rightMargin; + if (!f->parentFrame()) { + idealWidth = qMax(maxChildFrameWidth, layoutStruct.contentsWidth).toReal(); + idealWidth += marginWidth.toReal(); + } + + QFixed actualWidth = qMax(newContentsWidth, qMax(maxChildFrameWidth, layoutStruct.contentsWidth)); + fd->contentsWidth = actualWidth; + if (newContentsWidth <= 0) { // nowrap layout? + fd->contentsWidth = newContentsWidth; + } + + fd->minimumWidth = layoutStruct.minimumWidth; + fd->maximumWidth = layoutStruct.maximumWidth; + + fd->size.height = fd->contentsHeight == -1 + ? layoutStruct.y + fd->border + fd->padding + fd->bottomMargin + : fd->contentsHeight + 2*(fd->border + fd->padding) + fd->topMargin + fd->bottomMargin; + fd->size.width = actualWidth + marginWidth; + fd->sizeDirty = false; + if (layoutStruct.updateRectForFloats.isValid()) + layoutStruct.updateRect |= layoutStruct.updateRectForFloats; + return layoutStruct.updateRect; +} + +void QTextDocumentLayoutPrivate::layoutFlow(QTextFrame::Iterator it, QTextLayoutStruct *layoutStruct, + int layoutFrom, int layoutTo, QFixed width) +{ + LDEBUG << "layoutFlow from=" << layoutFrom << "to=" << layoutTo; + QTextFrameData *fd = data(layoutStruct->frame); + + fd->currentLayoutStruct = layoutStruct; + + QTextFrame::Iterator previousIt; + + const bool inRootFrame = (it.parentFrame() == document->rootFrame()); + if (inRootFrame) { + bool redoCheckPoints = layoutStruct->fullLayout || checkPoints.isEmpty(); + + if (!redoCheckPoints) { + QVector::Iterator checkPoint = qLowerBound(checkPoints.begin(), checkPoints.end(), layoutFrom); + if (checkPoint != checkPoints.end()) { + if (checkPoint != checkPoints.begin()) + --checkPoint; + + layoutStruct->y = checkPoint->y; + layoutStruct->frameY = checkPoint->frameY; + layoutStruct->minimumWidth = checkPoint->minimumWidth; + layoutStruct->maximumWidth = checkPoint->maximumWidth; + layoutStruct->contentsWidth = checkPoint->contentsWidth; + + if (layoutStruct->pageHeight > 0) { + int page = layoutStruct->currentPage(); + layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin; + } + + it = frameIteratorForTextPosition(checkPoint->positionInFrame); + checkPoints.resize(checkPoint - checkPoints.begin() + 1); + + if (checkPoint != checkPoints.begin()) { + previousIt = it; + --previousIt; + } + } else { + redoCheckPoints = true; + } + } + + if (redoCheckPoints) { + checkPoints.clear(); + QCheckPoint cp; + cp.y = layoutStruct->y; + cp.frameY = layoutStruct->frameY; + cp.positionInFrame = 0; + cp.minimumWidth = layoutStruct->minimumWidth; + cp.maximumWidth = layoutStruct->maximumWidth; + cp.contentsWidth = layoutStruct->contentsWidth; + checkPoints.append(cp); + } + } + + QTextBlockFormat previousBlockFormat = previousIt.currentBlock().blockFormat(); + + QFixed maximumBlockWidth = 0; + while (!it.atEnd()) { + QTextFrame *c = it.currentFrame(); + + int docPos; + if (it.currentFrame()) + docPos = it.currentFrame()->firstPosition(); + else + docPos = it.currentBlock().position(); + + if (inRootFrame) { + if (qAbs(layoutStruct->y - checkPoints.last().y) > 2000) { + QFixed left, right; + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + if (left == layoutStruct->x_left && right == layoutStruct->x_right) { + QCheckPoint p; + p.y = layoutStruct->y; + p.frameY = layoutStruct->frameY; + p.positionInFrame = docPos; + p.minimumWidth = layoutStruct->minimumWidth; + p.maximumWidth = layoutStruct->maximumWidth; + p.contentsWidth = layoutStruct->contentsWidth; + checkPoints.append(p); + + if (currentLazyLayoutPosition != -1 + && docPos > currentLazyLayoutPosition + lazyLayoutStepSize) + break; + + } + } + } + + if (c) { + // position child frame + QTextFrameData *cd = data(c); + + QTextFrameFormat fformat = c->frameFormat(); + + if (fformat.position() == QTextFrameFormat::InFlow) { + if (fformat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore) + layoutStruct->newPage(); + + QFixed left, right; + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + left = qMax(left, layoutStruct->x_left); + right = qMin(right, layoutStruct->x_right); + + if (right - left < cd->size.width) { + layoutStruct->y = findY(layoutStruct->y, layoutStruct, cd->size.width); + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + } + + QFixedPoint pos(left, layoutStruct->y); + + Qt::Alignment align = Qt::AlignLeft; + + QTextTable *table = qobject_cast(c); + + if (table) + align = table->format().alignment() & Qt::AlignHorizontal_Mask; + + // detect whether we have any alignment in the document that disallows optimizations, + // such as not laying out the document again in a textedit with wrapping disabled. + if (inRootFrame && !(align & Qt::AlignLeft)) + contentHasAlignment = true; + + cd->position = pos; + + if (document->pageSize().height() > 0.0f) + cd->sizeDirty = true; + + if (cd->sizeDirty) { + if (width != 0) + layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY); + else + layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY); + + QFixed absoluteChildPos = table ? pos.y + static_cast(data(table))->rowPositions.at(0) : pos.y + firstChildPos(c); + absoluteChildPos += layoutStruct->frameY; + + // drop entire frame to next page if first child of frame is on next page + if (absoluteChildPos > layoutStruct->pageBottom) { + layoutStruct->newPage(); + pos.y = layoutStruct->y; + + cd->position = pos; + cd->sizeDirty = true; + + if (width != 0) + layoutFrame(c, layoutFrom, layoutTo, width, -1, layoutStruct->frameY); + else + layoutFrame(c, layoutFrom, layoutTo, layoutStruct->frameY); + } + } + + // align only if there is space for alignment + if (right - left > cd->size.width) { + if (align & Qt::AlignRight) + pos.x += layoutStruct->x_right - cd->size.width; + else if (align & Qt::AlignHCenter) + pos.x += (layoutStruct->x_right - cd->size.width) / 2; + } + + cd->position = pos; + + layoutStruct->y += cd->size.height; + const int page = layoutStruct->currentPage(); + layoutStruct->pageBottom = (page + 1) * layoutStruct->pageHeight - layoutStruct->pageBottomMargin; + + cd->layoutDirty = false; + + if (c->frameFormat().pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter) + layoutStruct->newPage(); + } else { + QRectF oldFrameRect(cd->position.toPointF(), cd->size.toSizeF()); + QRectF updateRect; + + if (cd->sizeDirty) + updateRect = layoutFrame(c, layoutFrom, layoutTo); + + positionFloat(c); + + // If the size was made dirty when the position was set, layout again + if (cd->sizeDirty) + updateRect = layoutFrame(c, layoutFrom, layoutTo); + + QRectF frameRect(cd->position.toPointF(), cd->size.toSizeF()); + + if (frameRect == oldFrameRect && updateRect.isValid()) + updateRect.translate(cd->position.toPointF()); + else + updateRect = frameRect; + + layoutStruct->addUpdateRectForFloat(updateRect); + if (oldFrameRect.isValid()) + layoutStruct->addUpdateRectForFloat(oldFrameRect); + } + + layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, cd->minimumWidth); + layoutStruct->maximumWidth = qMin(layoutStruct->maximumWidth, cd->maximumWidth); + + previousIt = it; + ++it; + } else { + QTextFrame::Iterator lastIt; + if (!previousIt.atEnd()) + lastIt = previousIt; + previousIt = it; + QTextBlock block = it.currentBlock(); + ++it; + + const QTextBlockFormat blockFormat = block.blockFormat(); + + if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore) + layoutStruct->newPage(); + + const QFixed origY = layoutStruct->y; + const QFixed origPageBottom = layoutStruct->pageBottom; + const QFixed origMaximumWidth = layoutStruct->maximumWidth; + layoutStruct->maximumWidth = 0; + + const QTextBlockFormat *previousBlockFormatPtr = 0; + if (lastIt.currentBlock().isValid()) + previousBlockFormatPtr = &previousBlockFormat; + + // layout and position child block + layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr); + + // detect whether we have any alignment in the document that disallows optimizations, + // such as not laying out the document again in a textedit with wrapping disabled. + if (inRootFrame && !(block.layout()->textOption().alignment() & Qt::AlignLeft)) + contentHasAlignment = true; + + // if the block right before a table is empty 'hide' it by + // positioning it into the table border + if (isEmptyBlockBeforeTable(block, blockFormat, it)) { + const QTextBlock lastBlock = lastIt.currentBlock(); + const qreal lastBlockBottomMargin = lastBlock.isValid() ? lastBlock.blockFormat().bottomMargin() : 0.0f; + layoutStruct->y = origY + QFixed::fromReal(qMax(lastBlockBottomMargin, block.blockFormat().topMargin())); + layoutStruct->pageBottom = origPageBottom; + } else { + // if the block right after a table is empty then 'hide' it, too + if (isEmptyBlockAfterTable(block, lastIt.currentFrame())) { + QTextTableData *td = static_cast(data(lastIt.currentFrame())); + QTextLayout *layout = block.layout(); + + QPointF pos((td->position.x + td->size.width).toReal(), + (td->position.y + td->size.height).toReal() - layout->boundingRect().height()); + + layout->setPosition(pos); + layoutStruct->y = origY; + layoutStruct->pageBottom = origPageBottom; + } + + // if the block right after a table starts with a line separator, shift it up by one line + if (isLineSeparatorBlockAfterTable(block, lastIt.currentFrame())) { + QTextTableData *td = static_cast(data(lastIt.currentFrame())); + QTextLayout *layout = block.layout(); + + QFixed height = QFixed::fromReal(layout->lineAt(0).height()); + + if (layoutStruct->pageBottom == origPageBottom) { + layoutStruct->y -= height; + layout->setPosition(layout->position() - QPointF(0, height.toReal())); + } else { + // relayout block to correctly handle page breaks + layoutStruct->y = origY - height; + layoutStruct->pageBottom = origPageBottom; + layoutBlock(block, docPos, blockFormat, layoutStruct, layoutFrom, layoutTo, previousBlockFormatPtr); + } + + QPointF linePos((td->position.x + td->size.width).toReal(), + (td->position.y + td->size.height - height).toReal()); + + layout->lineAt(0).setPosition(linePos - layout->position()); + } + + if (blockFormat.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter) + layoutStruct->newPage(); + } + + maximumBlockWidth = qMax(maximumBlockWidth, layoutStruct->maximumWidth); + layoutStruct->maximumWidth = origMaximumWidth; + previousBlockFormat = blockFormat; + } + } + if (layoutStruct->maximumWidth == QFIXED_MAX && maximumBlockWidth > 0) + layoutStruct->maximumWidth = maximumBlockWidth; + else + layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maximumBlockWidth); + + // a float at the bottom of a frame may make it taller, hence the qMax() for layoutStruct->y. + // we don't need to do it for tables though because floats in tables are per table + // and not per cell and layoutCell already takes care of doing the same as we do here + if (!qobject_cast(layoutStruct->frame)) { + QList children = layoutStruct->frame->childFrames(); + for (int i = 0; i < children.count(); ++i) { + QTextFrameData *fd = data(children.at(i)); + if (!fd->layoutDirty && children.at(i)->frameFormat().position() != QTextFrameFormat::InFlow) + layoutStruct->y = qMax(layoutStruct->y, fd->position.y + fd->size.height); + } + } + + if (inRootFrame) { + // we assume that any float is aligned in a way that disallows the optimizations that rely + // on unaligned content. + if (!fd->floats.isEmpty()) + contentHasAlignment = true; + + if (it.atEnd()) { + //qDebug() << "layout done!"; + currentLazyLayoutPosition = -1; + QCheckPoint cp; + cp.y = layoutStruct->y; + cp.positionInFrame = docPrivate->length(); + cp.minimumWidth = layoutStruct->minimumWidth; + cp.maximumWidth = layoutStruct->maximumWidth; + cp.contentsWidth = layoutStruct->contentsWidth; + checkPoints.append(cp); + checkPoints.reserve(checkPoints.size()); + } else { + currentLazyLayoutPosition = checkPoints.last().positionInFrame; + // ####### + //checkPoints.last().positionInFrame = q->document()->docHandle()->length(); + } + } + + + fd->currentLayoutStruct = 0; +} + +static inline void getLineHeightParams(const QTextBlockFormat &blockFormat, const QTextLine &line, qreal scaling, + QFixed *lineAdjustment, QFixed *lineBreakHeight, QFixed *lineHeight) +{ + *lineHeight = QFixed::fromReal(blockFormat.lineHeight(line.height(), scaling)); + + if (blockFormat.lineHeightType() == QTextBlockFormat::FixedHeight || blockFormat.lineHeightType() == QTextBlockFormat::MinimumHeight) { + *lineBreakHeight = *lineHeight; + if (blockFormat.lineHeightType() == QTextBlockFormat::FixedHeight) + *lineAdjustment = QFixed::fromReal(line.ascent() + qMax(line.leading(), qreal(0.0))) - ((*lineHeight * 4) / 5); + else + *lineAdjustment = QFixed::fromReal(line.height()) - *lineHeight; + } + else { + *lineBreakHeight = QFixed::fromReal(line.height()); + *lineAdjustment = 0; + } +} + +void QTextDocumentLayoutPrivate::layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat, + QTextLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat) +{ + Q_Q(QTextDocumentLayout); + + QTextLayout *tl = bl.layout(); + const int blockLength = bl.length(); + + LDEBUG << "layoutBlock from=" << layoutFrom << "to=" << layoutTo; + +// qDebug() << "layoutBlock; width" << layoutStruct->x_right - layoutStruct->x_left << "(maxWidth is btw" << tl->maximumWidth() << ')'; + + if (previousBlockFormat) { + qreal margin = qMax(blockFormat.topMargin(), previousBlockFormat->bottomMargin()); + if (margin > 0 && q->paintDevice()) { + margin *= qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()); + } + layoutStruct->y += QFixed::fromReal(margin); + } + + //QTextFrameData *fd = data(layoutStruct->frame); + + Qt::LayoutDirection dir = bl.textDirection(); + + QFixed extraMargin; + if (docPrivate->defaultTextOption.flags() & QTextOption::AddSpaceForLineAndParagraphSeparators) { + QFontMetricsF fm(bl.charFormat().font()); + extraMargin = QFixed::fromReal(fm.width(QChar(QChar(0x21B5)))); + } + + const QFixed indent = this->blockIndent(blockFormat); + const QFixed totalLeftMargin = QFixed::fromReal(blockFormat.leftMargin()) + (dir == Qt::RightToLeft ? extraMargin : indent); + const QFixed totalRightMargin = QFixed::fromReal(blockFormat.rightMargin()) + (dir == Qt::RightToLeft ? indent : extraMargin); + + const QPointF oldPosition = tl->position(); + tl->setPosition(QPointF(layoutStruct->x_left.toReal(), layoutStruct->y.toReal())); + + if (layoutStruct->fullLayout + || (blockPosition + blockLength > layoutFrom && blockPosition <= layoutTo) + // force relayout if we cross a page boundary + || (layoutStruct->pageHeight != QFIXED_MAX && layoutStruct->absoluteY() + QFixed::fromReal(tl->boundingRect().height()) > layoutStruct->pageBottom)) { + + LDEBUG << " do layout"; + QTextOption option = docPrivate->defaultTextOption; + option.setTextDirection(dir); + option.setTabs( blockFormat.tabPositions() ); + + Qt::Alignment align = docPrivate->defaultTextOption.alignment(); + if (blockFormat.hasProperty(QTextFormat::BlockAlignment)) + align = blockFormat.alignment(); + option.setAlignment(QStyle::visualAlignment(dir, align)); // for paragraph that are RTL, alignment is auto-reversed; + + if (blockFormat.nonBreakableLines() || document->pageSize().width() < 0) { + option.setWrapMode(QTextOption::ManualWrap); + } + + tl->setTextOption(option); + + const bool haveWordOrAnyWrapMode = (option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere); + +// qDebug() << " layouting block at" << bl.position(); + const QFixed cy = layoutStruct->y; + const QFixed l = layoutStruct->x_left + totalLeftMargin; + const QFixed r = layoutStruct->x_right - totalRightMargin; + + tl->beginLayout(); + bool firstLine = true; + while (1) { + QTextLine line = tl->createLine(); + if (!line.isValid()) + break; + line.setLeadingIncluded(true); + + QFixed left, right; + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + left = qMax(left, l); + right = qMin(right, r); + QFixed text_indent; + if (firstLine) { + text_indent = QFixed::fromReal(blockFormat.textIndent()); + if (dir == Qt::LeftToRight) + left += text_indent; + else + right -= text_indent; + firstLine = false; + } +// qDebug() << "layout line y=" << currentYPos << "left=" << left << "right=" <textWidth" << line.textWidth(); + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + left = qMax(left, l); + right = qMin(right, r); + if (dir == Qt::LeftToRight) + left += text_indent; + else + right -= text_indent; + + if (fixedColumnWidth == -1 && QFixed::fromReal(line.naturalTextWidth()) > right-left) { + // float has been added in the meantime, redo + layoutStruct->pendingFloats.clear(); + + line.setLineWidth((right-left).toReal()); + if (QFixed::fromReal(line.naturalTextWidth()) > right-left) { + if (haveWordOrAnyWrapMode) { + option.setWrapMode(QTextOption::WrapAnywhere); + tl->setTextOption(option); + } + + layoutStruct->pendingFloats.clear(); + // lines min width more than what we have + layoutStruct->y = findY(layoutStruct->y, layoutStruct, QFixed::fromReal(line.naturalTextWidth())); + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + left = qMax(left, l); + right = qMin(right, r); + if (dir == Qt::LeftToRight) + left += text_indent; + else + right -= text_indent; + line.setLineWidth(qMax(line.naturalTextWidth(), (right-left).toReal())); + + if (haveWordOrAnyWrapMode) { + option.setWrapMode(QTextOption::WordWrap); + tl->setTextOption(option); + } + } + + } + + QFixed lineBreakHeight, lineHeight, lineAdjustment; + qreal scaling = (q->paintDevice() && q->paintDevice()->logicalDpiY() != qt_defaultDpi()) ? + qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()) : 1; + getLineHeightParams(blockFormat, line, scaling, &lineAdjustment, &lineBreakHeight, &lineHeight); + + if (layoutStruct->pageHeight > 0 && layoutStruct->absoluteY() + lineBreakHeight > layoutStruct->pageBottom) { + layoutStruct->newPage(); + + floatMargins(layoutStruct->y, layoutStruct, &left, &right); + left = qMax(left, l); + right = qMin(right, r); + if (dir == Qt::LeftToRight) + left += text_indent; + else + right -= text_indent; + } + + line.setPosition(QPointF((left - layoutStruct->x_left).toReal(), (layoutStruct->y - cy - lineAdjustment).toReal())); + layoutStruct->y += lineHeight; + layoutStruct->contentsWidth + = qMax(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + line.naturalTextWidth()) + totalRightMargin); + + // position floats + for (int i = 0; i < layoutStruct->pendingFloats.size(); ++i) { + QTextFrame *f = layoutStruct->pendingFloats.at(i); + positionFloat(f); + } + layoutStruct->pendingFloats.clear(); + } + tl->endLayout(); + } else { + const int cnt = tl->lineCount(); + for (int i = 0; i < cnt; ++i) { + LDEBUG << "going to move text line" << i; + QTextLine line = tl->lineAt(i); + layoutStruct->contentsWidth + = qMax(layoutStruct->contentsWidth, QFixed::fromReal(line.x() + tl->lineAt(i).naturalTextWidth()) + totalRightMargin); + + QFixed lineBreakHeight, lineHeight, lineAdjustment; + qreal scaling = (q->paintDevice() && q->paintDevice()->logicalDpiY() != qt_defaultDpi()) ? + qreal(q->paintDevice()->logicalDpiY()) / qreal(qt_defaultDpi()) : 1; + getLineHeightParams(blockFormat, line, scaling, &lineAdjustment, &lineBreakHeight, &lineHeight); + + if (layoutStruct->pageHeight != QFIXED_MAX) { + if (layoutStruct->absoluteY() + lineBreakHeight > layoutStruct->pageBottom) + layoutStruct->newPage(); + line.setPosition(QPointF(line.position().x(), (layoutStruct->y - lineAdjustment).toReal() - tl->position().y())); + } + layoutStruct->y += lineHeight; + } + if (layoutStruct->updateRect.isValid() + && blockLength > 1) { + if (layoutFrom >= blockPosition + blockLength) { + // if our height didn't change and the change in the document is + // in one of the later paragraphs, then we don't need to repaint + // this one + layoutStruct->updateRect.setTop(qMax(layoutStruct->updateRect.top(), layoutStruct->y.toReal())); + } else if (layoutTo < blockPosition) { + if (oldPosition == tl->position()) + // if the change in the document happened earlier in the document + // and our position did /not/ change because none of the earlier paragraphs + // or frames changed their height, then we don't need to repaint + // this one + layoutStruct->updateRect.setBottom(qMin(layoutStruct->updateRect.bottom(), tl->position().y())); + else + layoutStruct->updateRect.setBottom(qreal(INT_MAX)); // reset + } + } + } + + // ### doesn't take floats into account. would need to do it per line. but how to retrieve then? (Simon) + const QFixed margins = totalLeftMargin + totalRightMargin; + layoutStruct->minimumWidth = qMax(layoutStruct->minimumWidth, QFixed::fromReal(tl->minimumWidth()) + margins); + + const QFixed maxW = QFixed::fromReal(tl->maximumWidth()) + margins; + + if (maxW > 0) { + if (layoutStruct->maximumWidth == QFIXED_MAX) + layoutStruct->maximumWidth = maxW; + else + layoutStruct->maximumWidth = qMax(layoutStruct->maximumWidth, maxW); + } +} + +void QTextDocumentLayoutPrivate::floatMargins(const QFixed &y, const QTextLayoutStruct *layoutStruct, + QFixed *left, QFixed *right) const +{ +// qDebug() << "floatMargins y=" << y; + *left = layoutStruct->x_left; + *right = layoutStruct->x_right; + QTextFrameData *lfd = data(layoutStruct->frame); + for (int i = 0; i < lfd->floats.size(); ++i) { + QTextFrameData *fd = data(lfd->floats.at(i)); + if (!fd->layoutDirty) { + if (fd->position.y <= y && fd->position.y + fd->size.height > y) { +// qDebug() << "adjusting with float" << f << fd->position.x()<< fd->size.width(); + if (lfd->floats.at(i)->frameFormat().position() == QTextFrameFormat::FloatLeft) + *left = qMax(*left, fd->position.x + fd->size.width); + else + *right = qMin(*right, fd->position.x); + } + } + } +// qDebug() << "floatMargins: left="<<*left<<"right="<<*right<<"y="<x_right - layoutStruct->x_left); + +// qDebug() << "findY:" << yFrom; + while (1) { + floatMargins(yFrom, layoutStruct, &left, &right); +// qDebug() << " yFrom=" << yFrom<<"right=" << right << "left=" << left << "requiredWidth=" << requiredWidth; + if (right-left >= requiredWidth) + break; + + // move float down until we find enough space + QFixed newY = QFIXED_MAX; + QTextFrameData *lfd = data(layoutStruct->frame); + for (int i = 0; i < lfd->floats.size(); ++i) { + QTextFrameData *fd = data(lfd->floats.at(i)); + if (!fd->layoutDirty) { + if (fd->position.y <= yFrom && fd->position.y + fd->size.height > yFrom) + newY = qMin(newY, fd->position.y + fd->size.height); + } + } + if (newY == QFIXED_MAX) + break; + yFrom = newY; + } + return yFrom; +} + +QTextDocumentLayout::QTextDocumentLayout(QTextDocument *doc) + : QAbstractTextDocumentLayout(*new QTextDocumentLayoutPrivate, doc) +{ + registerHandler(QTextFormat::ImageObject, new QTextImageHandler(this)); +} + + +void QTextDocumentLayout::draw(QPainter *painter, const PaintContext &context) +{ + Q_D(QTextDocumentLayout); + QTextFrame *frame = d->document->rootFrame(); + QTextFrameData *fd = data(frame); + + if(fd->sizeDirty) + return; + + if (context.clip.isValid()) { + d->ensureLayouted(QFixed::fromReal(context.clip.bottom())); + } else { + d->ensureLayoutFinished(); + } + + QFixed width = fd->size.width; + if (d->document->pageSize().width() == 0 && d->viewportRect.isValid()) { + // we're in NoWrap mode, meaning the frame should expand to the viewport + // so that backgrounds are drawn correctly + fd->size.width = qMax(width, QFixed::fromReal(d->viewportRect.right())); + } + + // Make sure we conform to the root frames bounds when drawing. + d->clipRect = QRectF(fd->position.toPointF(), fd->size.toSizeF()).adjusted(fd->leftMargin.toReal(), 0, -fd->rightMargin.toReal(), 0); + d->drawFrame(QPointF(), painter, context, frame); + fd->size.width = width; +} + +void QTextDocumentLayout::setViewport(const QRectF &viewport) +{ + Q_D(QTextDocumentLayout); + d->viewportRect = viewport; +} + +static void markFrames(QTextFrame *current, int from, int oldLength, int length) +{ + int end = qMax(oldLength, length) + from; + + if (current->firstPosition() >= end || current->lastPosition() < from) + return; + + QTextFrameData *fd = data(current); + for (int i = 0; i < fd->floats.size(); ++i) { + QTextFrame *f = fd->floats[i]; + if (!f) { + // float got removed in editing operation + fd->floats.removeAt(i); + --i; + } + } + + fd->layoutDirty = true; + fd->sizeDirty = true; + +// qDebug(" marking frame (%d--%d) as dirty", current->firstPosition(), current->lastPosition()); + QList children = current->childFrames(); + for (int i = 0; i < children.size(); ++i) + markFrames(children.at(i), from, oldLength, length); +} + +void QTextDocumentLayout::documentChanged(int from, int oldLength, int length) +{ + Q_D(QTextDocumentLayout); + + QTextBlock blockIt = document()->findBlock(from); + QTextBlock endIt = document()->findBlock(qMax(0, from + length - 1)); + if (endIt.isValid()) + endIt = endIt.next(); + for (; blockIt.isValid() && blockIt != endIt; blockIt = blockIt.next()) + blockIt.clearLayout(); + + if (d->docPrivate->pageSize.isNull()) + return; + + QRectF updateRect; + + d->lazyLayoutStepSize = 1000; + d->sizeChangedTimer.stop(); + d->insideDocumentChange = true; + + const int documentLength = d->docPrivate->length(); + const bool fullLayout = (oldLength == 0 && length == documentLength); + const bool smallChange = documentLength > 0 + && (qMax(length, oldLength) * 100 / documentLength) < 5; + + // don't show incremental layout progress (avoid scroll bar flicker) + // if we see only a small change in the document and we're either starting + // a layout run or we're already in progress for that and we haven't seen + // any bigger change previously (showLayoutProgress already false) + if (smallChange + && (d->currentLazyLayoutPosition == -1 || d->showLayoutProgress == false)) + d->showLayoutProgress = false; + else + d->showLayoutProgress = true; + + if (fullLayout) { + d->contentHasAlignment = false; + d->currentLazyLayoutPosition = 0; + d->checkPoints.clear(); + d->layoutStep(); + } else { + d->ensureLayoutedByPosition(from); + updateRect = doLayout(from, oldLength, length); + } + + if (!d->layoutTimer.isActive() && d->currentLazyLayoutPosition != -1) + d->layoutTimer.start(10, this); + + d->insideDocumentChange = false; + + if (d->showLayoutProgress) { + const QSizeF newSize = dynamicDocumentSize(); + if (newSize != d->lastReportedSize) { + d->lastReportedSize = newSize; + emit documentSizeChanged(newSize); + } + } + + if (!updateRect.isValid()) { + // don't use the frame size, it might have shrunken + updateRect = QRectF(QPointF(0, 0), QSizeF(qreal(INT_MAX), qreal(INT_MAX))); + } + + emit update(updateRect); +} + +QRectF QTextDocumentLayout::doLayout(int from, int oldLength, int length) +{ + Q_D(QTextDocumentLayout); + +// qDebug("documentChange: from=%d, oldLength=%d, length=%d", from, oldLength, length); + + // mark all frames between f_start and f_end as dirty + markFrames(d->docPrivate->rootFrame(), from, oldLength, length); + + QRectF updateRect; + + QTextFrame *root = d->docPrivate->rootFrame(); + if(data(root)->sizeDirty) + updateRect = d->layoutFrame(root, from, from + length); + data(root)->layoutDirty = false; + + if (d->currentLazyLayoutPosition == -1) + layoutFinished(); + else if (d->showLayoutProgress) + d->sizeChangedTimer.start(0, this); + + return updateRect; +} + +int QTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const +{ + Q_D(const QTextDocumentLayout); + d->ensureLayouted(QFixed::fromReal(point.y())); + QTextFrame *f = d->docPrivate->rootFrame(); + int position = 0; + QTextLayout *l = 0; + QFixedPoint pointf; + pointf.x = QFixed::fromReal(point.x()); + pointf.y = QFixed::fromReal(point.y()); + QTextDocumentLayoutPrivate::HitPoint p = d->hitTest(f, pointf, &position, &l, accuracy); + if (accuracy == Qt::ExactHit && p < QTextDocumentLayoutPrivate::PointExact) + return -1; + + // ensure we stay within document bounds + int lastPos = f->lastPosition(); + if (l && !l->preeditAreaText().isEmpty()) + lastPos += l->preeditAreaText().length(); + if (position > lastPos) + position = lastPos; + else if (position < 0) + position = 0; + + return position; +} + +void QTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format) +{ + Q_D(QTextDocumentLayout); + QTextCharFormat f = format.toCharFormat(); + Q_ASSERT(f.isValid()); + QTextObjectHandler handler = d->handlers.value(f.objectType()); + if (!handler.component) + return; + + QSizeF intrinsic = handler.iface->intrinsicSize(d->document, posInDocument, format); + + QTextFrameFormat::Position pos = QTextFrameFormat::InFlow; + QTextFrame *frame = qobject_cast(d->document->objectForFormat(f)); + if (frame) { + pos = frame->frameFormat().position(); + QTextFrameData *fd = data(frame); + fd->sizeDirty = false; + fd->size = QFixedSize::fromSizeF(intrinsic); + fd->minimumWidth = fd->maximumWidth = fd->size.width; + } + + QSizeF inlineSize = (pos == QTextFrameFormat::InFlow ? intrinsic : QSizeF(0, 0)); + item.setWidth(inlineSize.width()); + if (f.verticalAlignment() == QTextCharFormat::AlignMiddle) { + item.setDescent(inlineSize.height() / 2); + item.setAscent(inlineSize.height() / 2 - 1); + } else { + item.setDescent(0); + item.setAscent(inlineSize.height() - 1); + } +} + +void QTextDocumentLayout::positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format) +{ + Q_D(QTextDocumentLayout); + Q_UNUSED(posInDocument); + if (item.width() != 0) + // inline + return; + + QTextCharFormat f = format.toCharFormat(); + Q_ASSERT(f.isValid()); + QTextObjectHandler handler = d->handlers.value(f.objectType()); + if (!handler.component) + return; + + QTextFrame *frame = qobject_cast(d->document->objectForFormat(f)); + if (!frame) + return; + + QTextBlock b = d->document->findBlock(frame->firstPosition()); + QTextLine line; + if (b.position() <= frame->firstPosition() && b.position() + b.length() > frame->lastPosition()) + line = b.layout()->lineAt(b.layout()->lineCount()-1); +// qDebug() << "layoutObject: line.isValid" << line.isValid() << b.position() << b.length() << +// frame->firstPosition() << frame->lastPosition(); + d->positionFloat(frame, line.isValid() ? &line : 0); +} + +void QTextDocumentLayout::drawInlineObject(QPainter *p, const QRectF &rect, QTextInlineObject item, + int posInDocument, const QTextFormat &format) +{ + Q_D(QTextDocumentLayout); + QTextCharFormat f = format.toCharFormat(); + Q_ASSERT(f.isValid()); + QTextFrame *frame = qobject_cast(d->document->objectForFormat(f)); + if (frame && frame->frameFormat().position() != QTextFrameFormat::InFlow) + return; // don't draw floating frames from inline objects here but in drawFlow instead + +// qDebug() << "drawObject at" << r; + QAbstractTextDocumentLayout::drawInlineObject(p, rect, item, posInDocument, format); +} + +int QTextDocumentLayout::dynamicPageCount() const +{ + Q_D(const QTextDocumentLayout); + const QSizeF pgSize = d->document->pageSize(); + if (pgSize.height() < 0) + return 1; + return qCeil(dynamicDocumentSize().height() / pgSize.height()); +} + +QSizeF QTextDocumentLayout::dynamicDocumentSize() const +{ + Q_D(const QTextDocumentLayout); + return data(d->docPrivate->rootFrame())->size.toSizeF(); +} + +int QTextDocumentLayout::pageCount() const +{ + Q_D(const QTextDocumentLayout); + d->ensureLayoutFinished(); + return dynamicPageCount(); +} + +QSizeF QTextDocumentLayout::documentSize() const +{ + Q_D(const QTextDocumentLayout); + d->ensureLayoutFinished(); + return dynamicDocumentSize(); +} + +void QTextDocumentLayoutPrivate::ensureLayouted(QFixed y) const +{ + Q_Q(const QTextDocumentLayout); + if (currentLazyLayoutPosition == -1) + return; + const QSizeF oldSize = q->dynamicDocumentSize(); + + if (checkPoints.isEmpty()) + layoutStep(); + + while (currentLazyLayoutPosition != -1 + && checkPoints.last().y < y) + layoutStep(); +} + +void QTextDocumentLayoutPrivate::ensureLayoutedByPosition(int position) const +{ + if (currentLazyLayoutPosition == -1) + return; + if (position < currentLazyLayoutPosition) + return; + while (currentLazyLayoutPosition != -1 + && currentLazyLayoutPosition < position) { + const_cast(q_func())->doLayout(currentLazyLayoutPosition, 0, INT_MAX - currentLazyLayoutPosition); + } +} + +void QTextDocumentLayoutPrivate::layoutStep() const +{ + ensureLayoutedByPosition(currentLazyLayoutPosition + lazyLayoutStepSize); + lazyLayoutStepSize = qMin(200000, lazyLayoutStepSize * 2); +} + +void QTextDocumentLayout::setCursorWidth(int width) +{ + Q_D(QTextDocumentLayout); + d->cursorWidth = width; +} + +int QTextDocumentLayout::cursorWidth() const +{ + Q_D(const QTextDocumentLayout); + return d->cursorWidth; +} + +void QTextDocumentLayout::setFixedColumnWidth(int width) +{ + Q_D(QTextDocumentLayout); + d->fixedColumnWidth = width; +} + +QRectF QTextDocumentLayout::frameBoundingRect(QTextFrame *frame) const +{ + Q_D(const QTextDocumentLayout); + if (d->docPrivate->pageSize.isNull()) + return QRectF(); + d->ensureLayoutFinished(); + return d->frameBoundingRectInternal(frame); +} + +QRectF QTextDocumentLayoutPrivate::frameBoundingRectInternal(QTextFrame *frame) const +{ + QPointF pos; + const int framePos = frame->firstPosition(); + QTextFrame *f = frame; + while (f) { + QTextFrameData *fd = data(f); + pos += fd->position.toPointF(); + + if (QTextTable *table = qobject_cast(f)) { + QTextTableCell cell = table->cellAt(framePos); + if (cell.isValid()) + pos += static_cast(fd)->cellPosition(cell).toPointF(); + } + + f = f->parentFrame(); + } + return QRectF(pos, data(frame)->size.toSizeF()); +} + +QRectF QTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const +{ + Q_D(const QTextDocumentLayout); + if (d->docPrivate->pageSize.isNull() || !block.isValid()) + return QRectF(); + d->ensureLayoutedByPosition(block.position() + block.length()); + QTextFrame *frame = d->document->frameAt(block.position()); + QPointF offset; + const int blockPos = block.position(); + + while (frame) { + QTextFrameData *fd = data(frame); + offset += fd->position.toPointF(); + + if (QTextTable *table = qobject_cast(frame)) { + QTextTableCell cell = table->cellAt(blockPos); + if (cell.isValid()) + offset += static_cast(fd)->cellPosition(cell).toPointF(); + } + + frame = frame->parentFrame(); + } + + const QTextLayout *layout = block.layout(); + QRectF rect = layout->boundingRect(); + rect.moveTopLeft(layout->position() + offset); + return rect; +} + +int QTextDocumentLayout::layoutStatus() const +{ + Q_D(const QTextDocumentLayout); + int pos = d->currentLazyLayoutPosition; + if (pos == -1) + return 100; + return pos * 100 / d->document->docHandle()->length(); +} + +void QTextDocumentLayout::timerEvent(QTimerEvent *e) +{ + Q_D(QTextDocumentLayout); + if (e->timerId() == d->layoutTimer.timerId()) { + if (d->currentLazyLayoutPosition != -1) + d->layoutStep(); + } else if (e->timerId() == d->sizeChangedTimer.timerId()) { + d->lastReportedSize = dynamicDocumentSize(); + emit documentSizeChanged(d->lastReportedSize); + d->sizeChangedTimer.stop(); + + if (d->currentLazyLayoutPosition == -1) { + const int newCount = dynamicPageCount(); + if (newCount != d->lastPageCount) { + d->lastPageCount = newCount; + emit pageCountChanged(newCount); + } + } + } else { + QAbstractTextDocumentLayout::timerEvent(e); + } +} + +void QTextDocumentLayout::layoutFinished() +{ + Q_D(QTextDocumentLayout); + d->layoutTimer.stop(); + if (!d->insideDocumentChange) + d->sizeChangedTimer.start(0, this); + // reset + d->showLayoutProgress = true; +} + +void QTextDocumentLayout::ensureLayouted(qreal y) +{ + d_func()->ensureLayouted(QFixed::fromReal(y)); +} + +qreal QTextDocumentLayout::idealWidth() const +{ + Q_D(const QTextDocumentLayout); + d->ensureLayoutFinished(); + return d->idealWidth; +} + +bool QTextDocumentLayout::contentHasAlignment() const +{ + Q_D(const QTextDocumentLayout); + return d->contentHasAlignment; +} + +qreal QTextDocumentLayoutPrivate::scaleToDevice(qreal value) const +{ + if (!paintDevice) + return value; + return value * paintDevice->logicalDpiY() / qreal(qt_defaultDpi()); +} + +QFixed QTextDocumentLayoutPrivate::scaleToDevice(QFixed value) const +{ + if (!paintDevice) + return value; + return value * QFixed(paintDevice->logicalDpiY()) / QFixed(qt_defaultDpi()); +} + +QT_END_NAMESPACE + +#include "moc_qtextdocumentlayout_p.cpp" diff --git a/src/gui/text/qtextdocumentlayout_p.h b/src/gui/text/qtextdocumentlayout_p.h new file mode 100644 index 0000000000..3c0383c3a4 --- /dev/null +++ b/src/gui/text/qtextdocumentlayout_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTDOCUMENTLAYOUT_P_H +#define QTEXTDOCUMENTLAYOUT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qabstracttextdocumentlayout.h" +#include "QtGui/qtextoption.h" +#include "QtGui/qtextobject.h" + +QT_BEGIN_NAMESPACE + +class QTextListFormat; + +class QTextDocumentLayoutPrivate; + +class Q_AUTOTEST_EXPORT QTextDocumentLayout : public QAbstractTextDocumentLayout +{ + Q_DECLARE_PRIVATE(QTextDocumentLayout) + Q_OBJECT + Q_PROPERTY(int cursorWidth READ cursorWidth WRITE setCursorWidth) + Q_PROPERTY(qreal idealWidth READ idealWidth) + Q_PROPERTY(bool contentHasAlignment READ contentHasAlignment) +public: + explicit QTextDocumentLayout(QTextDocument *doc); + + // from the abstract layout + void draw(QPainter *painter, const PaintContext &context); + int hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const; + + int pageCount() const; + QSizeF documentSize() const; + + void setCursorWidth(int width); + int cursorWidth() const; + + // internal, to support the ugly FixedColumnWidth wordwrap mode in QTextEdit + void setFixedColumnWidth(int width); + + // internal for QTextEdit's NoWrap mode + void setViewport(const QRectF &viewport); + + virtual QRectF frameBoundingRect(QTextFrame *frame) const; + virtual QRectF blockBoundingRect(const QTextBlock &block) const; + + // #### + int layoutStatus() const; + int dynamicPageCount() const; + QSizeF dynamicDocumentSize() const; + void ensureLayouted(qreal); + + qreal idealWidth() const; + + bool contentHasAlignment() const; + +protected: + void documentChanged(int from, int oldLength, int length); + void resizeInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format); + void positionInlineObject(QTextInlineObject item, int posInDocument, const QTextFormat &format); + void drawInlineObject(QPainter *p, const QRectF &rect, QTextInlineObject item, + int posInDocument, const QTextFormat &format); + virtual void timerEvent(QTimerEvent *e); +private: + QRectF doLayout(int from, int oldLength, int length); + void layoutFinished(); +}; + +QT_END_NAMESPACE + +#endif // QTEXTDOCUMENTLAYOUT_P_H diff --git a/src/gui/text/qtextdocumentwriter.cpp b/src/gui/text/qtextdocumentwriter.cpp new file mode 100644 index 0000000000..fe91a5511c --- /dev/null +++ b/src/gui/text/qtextdocumentwriter.cpp @@ -0,0 +1,372 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qtextdocumentwriter.h" + +#include +#include +#include +#include +#include +#include +#include "qtextdocument.h" +#include "qtextdocumentfragment.h" + +#include "qtextdocumentfragment_p.h" +#include "qtextodfwriter_p.h" + +QT_BEGIN_NAMESPACE + +class QTextDocumentWriterPrivate +{ +public: + QTextDocumentWriterPrivate(QTextDocumentWriter* qq); + + // device + QByteArray format; + QIODevice *device; + bool deleteDevice; +#ifndef QT_NO_TEXTCODEC + QTextCodec *codec; +#endif + + QTextDocumentWriter *q; +}; + +/*! + \since 4.5 + \class QTextDocumentWriter + + \brief The QTextDocumentWriter class provides a format-independent interface for writing a QTextDocument to files or other devices. + + \ingroup richtext-processing + \ingroup io + + To write a document, construct a QTextDocumentWriter object with either a + file name or a device object, and specify the document format to be + written. You can construct a writer and set the format using setFormat() + later. + + Call write() to write the document to the device. If the document is + successfully written, this function returns true. However, if an error + occurs when writing the document, it will return false. + + Call supportedDocumentFormats() for a list of formats that + QTextDocumentWriter can write. + + Since the capabilities of the supported output formats vary considerably, + the writer simply outputs the appropriate subset of objects for each format. + This typically includes the formatted text and images contained in a + document. +*/ + +/*! + \internal +*/ +QTextDocumentWriterPrivate::QTextDocumentWriterPrivate(QTextDocumentWriter *qq) + : device(0), + deleteDevice(false), +#ifndef QT_NO_TEXTCODEC + codec(QTextCodec::codecForName("utf-8")), +#endif + q(qq) +{ +} + +/*! + Constructs an empty QTextDocumentWriter object. Before writing, you must + call setFormat() to set a document format, then setDevice() or + setFileName(). +*/ +QTextDocumentWriter::QTextDocumentWriter() + : d(new QTextDocumentWriterPrivate(this)) +{ +} + +/*! + Constructs a QTextDocumentWriter object to write to the given \a device + in the document format specified by \a format. +*/ +QTextDocumentWriter::QTextDocumentWriter(QIODevice *device, const QByteArray &format) + : d(new QTextDocumentWriterPrivate(this)) +{ + d->device = device; + d->format = format; +} + +/*! + Constructs an QTextDocumentWriter object that will write to a file with + the name \a fileName, using the document format specified by \a format. + If \a format is not provided, QTextDocumentWriter will detect the document + format by inspecting the extension of \a fileName. +*/ +QTextDocumentWriter::QTextDocumentWriter(const QString &fileName, const QByteArray &format) + : d(new QTextDocumentWriterPrivate(this)) +{ + QFile *file = new QFile(fileName); + d->device = file; + d->deleteDevice = true; + d->format = format; +} + +/*! + Destroys the QTextDocumentWriter object. +*/ +QTextDocumentWriter::~QTextDocumentWriter() +{ + if (d->deleteDevice) + delete d->device; + delete d; +} + +/*! + Sets the format used to write documents to the \a format specified. + \a format is a case insensitive text string. For example: + + \snippet doc/src/snippets/code/src.gui.text.qtextdocumentwriter.cpp 0 + + You can call supportedDocumentFormats() for the full list of formats + QTextDocumentWriter supports. + + \sa format() +*/ +void QTextDocumentWriter::setFormat (const QByteArray &format) +{ + d->format = format; +} + +/*! + Returns the format used for writing documents. + + \sa setFormat() +*/ +QByteArray QTextDocumentWriter::format () const +{ + return d->format; +} + +/*! + Sets the writer's device to the \a device specified. If a device has + already been set, the old device is removed but otherwise left + unchanged. + + If the device is not already open, QTextDocumentWriter will attempt to + open the device in \l QIODevice::WriteOnly mode by calling open(). + + \note This will not work for certain devices, such as QProcess, + QTcpSocket and QUdpSocket, where some configuration is required before + the device can be opened. + + \sa device(), setFileName() +*/ +void QTextDocumentWriter::setDevice (QIODevice *device) +{ + if (d->device && d->deleteDevice) + delete d->device; + + d->device = device; + d->deleteDevice = false; +} + +/*! + Returns the device currently assigned, or 0 if no device has been + assigned. +*/ +QIODevice *QTextDocumentWriter::device () const +{ + return d->device; +} + +/*! + Sets the name of the file to be written to \a fileName. Internally, + QTextDocumentWriter will create a QFile and open it in \l + QIODevice::WriteOnly mode, and use this file when writing the document. + + \sa fileName(), setDevice() +*/ +void QTextDocumentWriter::setFileName (const QString &fileName) +{ + setDevice(new QFile(fileName)); + d->deleteDevice = true; +} + +/*! + If the currently assigned device is a QFile, or if setFileName() + has been called, this function returns the name of the file + to be written to. In all other cases, it returns an empty string. + + \sa setFileName(), setDevice() +*/ +QString QTextDocumentWriter::fileName () const +{ + QFile *file = qobject_cast(d->device); + return file ? file->fileName() : QString(); +} + +/*! + Writes the given \a document to the assigned device or file and + returns true if successful; otherwise returns false. +*/ +bool QTextDocumentWriter::write(const QTextDocument *document) +{ + QByteArray suffix; + + if (d->device && d->format.isEmpty()) { + // if there's no format, see if device is a file, and if so, find + // the file suffix + if (QFile *file = qobject_cast(d->device)) + suffix = QFileInfo(file->fileName()).suffix().toLower().toLatin1(); + } + + QByteArray format = !d->format.isEmpty() ? d->format.toLower() : suffix; + +#ifndef QT_NO_TEXTODFWRITER + if (format == "odf" || format == "opendocumentformat" || format == "odt") { + QTextOdfWriter writer(*document, d->device); +#ifndef QT_NO_TEXTCODEC + writer.setCodec(d->codec); +#endif + return writer.writeAll(); + } +#endif // QT_NO_TEXTODFWRITER + +#ifndef QT_NO_TEXTHTMLPARSER + if (format == "html" || format == "htm") { + if (!d->device->isWritable() && ! d->device->open(QIODevice::WriteOnly)) { + qWarning() << "QTextDocumentWriter::write: the device can not be opened for writing"; + return false; + } + QTextStream ts(d->device); +#ifndef QT_NO_TEXTCODEC + ts.setCodec(d->codec); + ts << document->toHtml(d->codec->name()); +#endif + d->device->close(); + return true; + } +#endif + if (format == "txt" || format == "plaintext") { + if (!d->device->isWritable() && ! d->device->open(QIODevice::WriteOnly)) { + qWarning() << "QTextDocumentWriter::write: the device can not be opened for writing"; + return false; + } + QTextStream ts(d->device); +#ifndef QT_NO_TEXTCODEC + ts.setCodec(d->codec); +#endif + ts << document->toPlainText(); + d->device->close(); + return true; + } + + return false; +} + +/*! + Writes the document fragment specified by \a fragment to the assigned device + or file and returns true if successful; otherwise returns false. +*/ +bool QTextDocumentWriter::write(const QTextDocumentFragment &fragment) +{ + if (fragment.d == 0) + return false; // invalid fragment. + QTextDocument *doc = fragment.d->doc; + if (doc) + return write(doc); + return false; +} + +/*! + Sets the codec for this stream to \a codec. The codec is used for + encoding any data that is written. By default, QTextDocumentWriter + uses UTF-8. +*/ + +#ifndef QT_NO_TEXTCODEC +void QTextDocumentWriter::setCodec(QTextCodec *codec) +{ + if (codec == 0) + codec = QTextCodec::codecForName("UTF-8"); + Q_ASSERT(codec); + d->codec = codec; +} +#endif + +/*! + Returns the codec that is currently assigned to the writer. +*/ +#ifndef QT_NO_TEXTCODEC +QTextCodec *QTextDocumentWriter::codec() const +{ + return d->codec; +} +#endif + +/*! + Returns the list of document formats supported by QTextDocumentWriter. + + By default, Qt can write the following formats: + + \table + \header \o Format \o Description + \row \o plaintext \o Plain text + \row \o HTML \o HyperText Markup Language + \row \o ODF \o OpenDocument Format + \endtable + + \sa setFormat() +*/ +QList QTextDocumentWriter::supportedDocumentFormats() +{ + QList answer; + answer << "plaintext"; + +#ifndef QT_NO_TEXTHTMLPARSER + answer << "HTML"; +#endif +#ifndef QT_NO_TEXTODFWRITER + answer << "ODF"; +#endif // QT_NO_TEXTODFWRITER + + qSort(answer); + return answer; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextdocumentwriter.h b/src/gui/text/qtextdocumentwriter.h new file mode 100644 index 0000000000..3fb002cb24 --- /dev/null +++ b/src/gui/text/qtextdocumentwriter.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QTEXTDOCUMENTWRITER_H +#define QTEXTDOCUMENTWRITER_H + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QTextDocumentWriterPrivate; +class QIODevice; +class QByteArray; +class QTextDocument; +class QTextDocumentFragment; + +class Q_GUI_EXPORT QTextDocumentWriter +{ +public: + QTextDocumentWriter(); + QTextDocumentWriter(QIODevice *device, const QByteArray &format); + QTextDocumentWriter(const QString &fileName, const QByteArray &format = QByteArray()); + ~QTextDocumentWriter(); + + void setFormat (const QByteArray &format); + QByteArray format () const; + + void setDevice (QIODevice *device); + QIODevice *device () const; + void setFileName (const QString &fileName); + QString fileName () const; + + bool write(const QTextDocument *document); + bool write(const QTextDocumentFragment &fragment); + +#ifndef QT_NO_TEXTCODEC + void setCodec(QTextCodec *codec); + QTextCodec *codec() const; +#endif + + static QList supportedDocumentFormats(); + +private: + Q_DISABLE_COPY(QTextDocumentWriter) + QTextDocumentWriterPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp new file mode 100644 index 0000000000..08d0eca7ac --- /dev/null +++ b/src/gui/text/qtextengine.cpp @@ -0,0 +1,2845 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdebug.h" +#include "qtextformat.h" +#include "qtextformat_p.h" +#include "qtextengine_p.h" +#include "qabstracttextdocumentlayout.h" +#include "qtextlayout.h" +#include "qtextboundaryfinder.h" +#include "qvarlengtharray.h" +#include "qfont.h" +#include "qfont_p.h" +#include "qfontengine_p.h" +#include "qstring.h" +#include +#include "qtextdocument_p.h" +#include +#include + + +QT_BEGIN_NAMESPACE + +namespace { +// Helper class used in QTextEngine::itemize +// keep it out here to allow us to keep supporting various compilers. +class Itemizer { +public: + Itemizer(const QString &string, const QScriptAnalysis *analysis, QScriptItemArray &items) + : m_string(string), + m_analysis(analysis), + m_items(items), + m_splitter(0) + { + } + ~Itemizer() + { + delete m_splitter; + } + + /// generate the script items + /// The caps parameter is used to choose the algoritm of splitting text and assiging roles to the textitems + void generate(int start, int length, QFont::Capitalization caps) + { + if ((int)caps == (int)QFont::SmallCaps) + generateScriptItemsSmallCaps(reinterpret_cast(m_string.unicode()), start, length); + else if(caps == QFont::Capitalize) + generateScriptItemsCapitalize(start, length); + else if(caps != QFont::MixedCase) { + generateScriptItemsAndChangeCase(start, length, + caps == QFont::AllLowercase ? QScriptAnalysis::Lowercase : QScriptAnalysis::Uppercase); + } + else + generateScriptItems(start, length); + } + +private: + enum { MaxItemLength = 4096 }; + + void generateScriptItemsAndChangeCase(int start, int length, QScriptAnalysis::Flags flags) + { + generateScriptItems(start, length); + if (m_items.isEmpty()) // the next loop won't work in that case + return; + QScriptItemArray::Iterator iter = m_items.end(); + do { + iter--; + if (iter->analysis.flags < QScriptAnalysis::TabOrObject) + iter->analysis.flags = flags; + } while (iter->position > start); + } + + void generateScriptItems(int start, int length) + { + if (!length) + return; + const int end = start + length; + for (int i = start + 1; i < end; ++i) { + if ((m_analysis[i] == m_analysis[start]) + && m_analysis[i].flags < QScriptAnalysis::SpaceTabOrObject + && i - start < MaxItemLength) + continue; + m_items.append(QScriptItem(start, m_analysis[start])); + start = i; + } + m_items.append(QScriptItem(start, m_analysis[start])); + } + + void generateScriptItemsCapitalize(int start, int length) + { + if (!length) + return; + + if (!m_splitter) + m_splitter = new QTextBoundaryFinder(QTextBoundaryFinder::Word, + m_string.constData(), m_string.length(), + /*buffer*/0, /*buffer size*/0); + + m_splitter->setPosition(start); + QScriptAnalysis itemAnalysis = m_analysis[start]; + + if (m_splitter->boundaryReasons() & QTextBoundaryFinder::StartWord) { + itemAnalysis.flags = QScriptAnalysis::Uppercase; + m_splitter->toNextBoundary(); + } + + const int end = start + length; + for (int i = start + 1; i < end; ++i) { + + bool atWordBoundary = false; + + if (i == m_splitter->position()) { + if (m_splitter->boundaryReasons() & QTextBoundaryFinder::StartWord + && m_analysis[i].flags < QScriptAnalysis::TabOrObject) + atWordBoundary = true; + + m_splitter->toNextBoundary(); + } + + if (m_analysis[i] == itemAnalysis + && m_analysis[i].flags < QScriptAnalysis::TabOrObject + && !atWordBoundary + && i - start < MaxItemLength) + continue; + + m_items.append(QScriptItem(start, itemAnalysis)); + start = i; + itemAnalysis = m_analysis[start]; + + if (atWordBoundary) + itemAnalysis.flags = QScriptAnalysis::Uppercase; + } + m_items.append(QScriptItem(start, itemAnalysis)); + } + + void generateScriptItemsSmallCaps(const ushort *uc, int start, int length) + { + if (!length) + return; + bool lower = (QChar::category(uc[start]) == QChar::Letter_Lowercase); + const int end = start + length; + // split text into parts that are already uppercase and parts that are lowercase, and mark the latter to be uppercased later. + for (int i = start + 1; i < end; ++i) { + bool l = (QChar::category(uc[i]) == QChar::Letter_Lowercase); + if ((m_analysis[i] == m_analysis[start]) + && m_analysis[i].flags < QScriptAnalysis::TabOrObject + && l == lower + && i - start < MaxItemLength) + continue; + m_items.append(QScriptItem(start, m_analysis[start])); + if (lower) + m_items.last().analysis.flags = QScriptAnalysis::SmallCaps; + + start = i; + lower = l; + } + m_items.append(QScriptItem(start, m_analysis[start])); + if (lower) + m_items.last().analysis.flags = QScriptAnalysis::SmallCaps; + } + + const QString &m_string; + const QScriptAnalysis * const m_analysis; + QScriptItemArray &m_items; + QTextBoundaryFinder *m_splitter; +}; +} + + +// ---------------------------------------------------------------------------- +// +// The BiDi algorithm +// +// ---------------------------------------------------------------------------- + +#define BIDI_DEBUG 0 +#if (BIDI_DEBUG >= 1) +QT_BEGIN_INCLUDE_NAMESPACE +#include +QT_END_INCLUDE_NAMESPACE +using namespace std; + +static const char *directions[] = { + "DirL", "DirR", "DirEN", "DirES", "DirET", "DirAN", "DirCS", "DirB", "DirS", "DirWS", "DirON", + "DirLRE", "DirLRO", "DirAL", "DirRLE", "DirRLO", "DirPDF", "DirNSM", "DirBN" +}; + +#endif + +struct QBidiStatus { + QBidiStatus() { + eor = QChar::DirON; + lastStrong = QChar::DirON; + last = QChar:: DirON; + dir = QChar::DirON; + } + QChar::Direction eor; + QChar::Direction lastStrong; + QChar::Direction last; + QChar::Direction dir; +}; + +enum { MaxBidiLevel = 61 }; + +struct QBidiControl { + inline QBidiControl(bool rtl) + : cCtx(0), base(rtl ? 1 : 0), level(rtl ? 1 : 0), override(false) {} + + inline void embed(bool rtl, bool o = false) { + unsigned int toAdd = 1; + if((level%2 != 0) == rtl ) { + ++toAdd; + } + if (level + toAdd <= MaxBidiLevel) { + ctx[cCtx].level = level; + ctx[cCtx].override = override; + cCtx++; + override = o; + level += toAdd; + } + } + inline bool canPop() const { return cCtx != 0; } + inline void pdf() { + Q_ASSERT(cCtx); + --cCtx; + level = ctx[cCtx].level; + override = ctx[cCtx].override; + } + + inline QChar::Direction basicDirection() const { + return (base ? QChar::DirR : QChar:: DirL); + } + inline unsigned int baseLevel() const { + return base; + } + inline QChar::Direction direction() const { + return ((level%2) ? QChar::DirR : QChar:: DirL); + } + + struct { + unsigned int level; + bool override; + } ctx[MaxBidiLevel]; + unsigned int cCtx; + const unsigned int base; + unsigned int level; + bool override; +}; + + +static void appendItems(QScriptAnalysis *analysis, int &start, int &stop, const QBidiControl &control, QChar::Direction dir) +{ + if (start > stop) + return; + + int level = control.level; + + if(dir != QChar::DirON && !control.override) { + // add level of run (cases I1 & I2) + if(level % 2) { + if(dir == QChar::DirL || dir == QChar::DirAN || dir == QChar::DirEN) + level++; + } else { + if(dir == QChar::DirR) + level++; + else if(dir == QChar::DirAN || dir == QChar::DirEN) + level += 2; + } + } + +#if (BIDI_DEBUG >= 1) + qDebug("new run: dir=%s from %d, to %d level = %d override=%d", directions[dir], start, stop, level, control.override); +#endif + QScriptAnalysis *s = analysis + start; + const QScriptAnalysis *e = analysis + stop; + while (s <= e) { + s->bidiLevel = level; + ++s; + } + ++stop; + start = stop; +} + + +// creates the next QScript items. +static bool bidiItemize(QTextEngine *engine, QScriptAnalysis *analysis, QBidiControl &control) +{ + bool rightToLeft = (control.basicDirection() == 1); + bool hasBidi = rightToLeft; +#if BIDI_DEBUG >= 2 + qDebug() << "bidiItemize: rightToLeft=" << rightToLeft << engine->layoutData->string; +#endif + + int sor = 0; + int eor = -1; + + + int length = engine->layoutData->string.length(); + + const ushort *unicode = (const ushort *)engine->layoutData->string.unicode(); + int current = 0; + + QChar::Direction dir = rightToLeft ? QChar::DirR : QChar::DirL; + QBidiStatus status; + + QChar::Direction sdir = QChar::direction(*unicode); + if (sdir != QChar::DirL && sdir != QChar::DirR && sdir != QChar::DirEN && sdir != QChar::DirAN) + sdir = QChar::DirON; + else + dir = QChar::DirON; + status.eor = sdir; + status.lastStrong = rightToLeft ? QChar::DirR : QChar::DirL; + status.last = status.lastStrong; + status.dir = sdir; + + + while (current <= length) { + + QChar::Direction dirCurrent; + if (current == (int)length) + dirCurrent = control.basicDirection(); + else + dirCurrent = QChar::direction(unicode[current]); + +#if (BIDI_DEBUG >= 2) +// qDebug() << "pos=" << current << " dir=" << directions[dir] +// << " current=" << directions[dirCurrent] << " last=" << directions[status.last] +// << " eor=" << eor << '/' << directions[status.eor] +// << " sor=" << sor << " lastStrong=" +// << directions[status.lastStrong] +// << " level=" << (int)control.level << " override=" << (bool)control.override; +#endif + + switch(dirCurrent) { + + // embedding and overrides (X1-X9 in the BiDi specs) + case QChar::DirRLE: + case QChar::DirRLO: + case QChar::DirLRE: + case QChar::DirLRO: + { + bool rtl = (dirCurrent == QChar::DirRLE || dirCurrent == QChar::DirRLO); + hasBidi |= rtl; + bool override = (dirCurrent == QChar::DirLRO || dirCurrent == QChar::DirRLO); + + unsigned int level = control.level+1; + if ((level%2 != 0) == rtl) ++level; + if(level < MaxBidiLevel) { + eor = current-1; + appendItems(analysis, sor, eor, control, dir); + eor = current; + control.embed(rtl, override); + QChar::Direction edir = (rtl ? QChar::DirR : QChar::DirL); + dir = status.eor = edir; + status.lastStrong = edir; + } + break; + } + case QChar::DirPDF: + { + if (control.canPop()) { + if (dir != control.direction()) { + eor = current-1; + appendItems(analysis, sor, eor, control, dir); + dir = control.direction(); + } + eor = current; + appendItems(analysis, sor, eor, control, dir); + control.pdf(); + dir = QChar::DirON; status.eor = QChar::DirON; + status.last = control.direction(); + if (control.override) + dir = control.direction(); + else + dir = QChar::DirON; + status.lastStrong = control.direction(); + } + break; + } + + // strong types + case QChar::DirL: + if(dir == QChar::DirON) + dir = QChar::DirL; + switch(status.last) + { + case QChar::DirL: + eor = current; status.eor = QChar::DirL; break; + case QChar::DirR: + case QChar::DirAL: + case QChar::DirEN: + case QChar::DirAN: + if (eor >= 0) { + appendItems(analysis, sor, eor, control, dir); + dir = eor < length ? QChar::direction(unicode[eor]) : control.basicDirection(); + status.eor = dir; + } else { + eor = current; status.eor = dir; + } + break; + case QChar::DirES: + case QChar::DirET: + case QChar::DirCS: + case QChar::DirBN: + case QChar::DirB: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + if(dir != QChar::DirL) { + //last stuff takes embedding dir + if(control.direction() == QChar::DirR) { + if(status.eor != QChar::DirR) { + // AN or EN + appendItems(analysis, sor, eor, control, dir); + status.eor = QChar::DirON; + dir = QChar::DirR; + } + eor = current - 1; + appendItems(analysis, sor, eor, control, dir); + dir = eor < length ? QChar::direction(unicode[eor]) : control.basicDirection(); + status.eor = dir; + } else { + if(status.eor != QChar::DirL) { + appendItems(analysis, sor, eor, control, dir); + status.eor = QChar::DirON; + dir = QChar::DirL; + } else { + eor = current; status.eor = QChar::DirL; break; + } + } + } else { + eor = current; status.eor = QChar::DirL; + } + default: + break; + } + status.lastStrong = QChar::DirL; + break; + case QChar::DirAL: + case QChar::DirR: + hasBidi = true; + if(dir == QChar::DirON) dir = QChar::DirR; + switch(status.last) + { + case QChar::DirL: + case QChar::DirEN: + case QChar::DirAN: + if (eor >= 0) + appendItems(analysis, sor, eor, control, dir); + // fall through + case QChar::DirR: + case QChar::DirAL: + dir = QChar::DirR; eor = current; status.eor = QChar::DirR; break; + case QChar::DirES: + case QChar::DirET: + case QChar::DirCS: + case QChar::DirBN: + case QChar::DirB: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + if(status.eor != QChar::DirR && status.eor != QChar::DirAL) { + //last stuff takes embedding dir + if(control.direction() == QChar::DirR + || status.lastStrong == QChar::DirR || status.lastStrong == QChar::DirAL) { + appendItems(analysis, sor, eor, control, dir); + dir = QChar::DirR; status.eor = QChar::DirON; + eor = current; + } else { + eor = current - 1; + appendItems(analysis, sor, eor, control, dir); + dir = QChar::DirR; status.eor = QChar::DirON; + } + } else { + eor = current; status.eor = QChar::DirR; + } + default: + break; + } + status.lastStrong = dirCurrent; + break; + + // weak types: + + case QChar::DirNSM: + if (eor == current-1) + eor = current; + break; + case QChar::DirEN: + // if last strong was AL change EN to AN + if(status.lastStrong != QChar::DirAL) { + if(dir == QChar::DirON) { + if(status.lastStrong == QChar::DirL) + dir = QChar::DirL; + else + dir = QChar::DirEN; + } + switch(status.last) + { + case QChar::DirET: + if (status.lastStrong == QChar::DirR || status.lastStrong == QChar::DirAL) { + appendItems(analysis, sor, eor, control, dir); + status.eor = QChar::DirON; + dir = QChar::DirAN; + } + // fall through + case QChar::DirEN: + case QChar::DirL: + eor = current; + status.eor = dirCurrent; + break; + case QChar::DirR: + case QChar::DirAL: + case QChar::DirAN: + if (eor >= 0) + appendItems(analysis, sor, eor, control, dir); + else + eor = current; + status.eor = QChar::DirEN; + dir = QChar::DirAN; break; + case QChar::DirES: + case QChar::DirCS: + if(status.eor == QChar::DirEN || dir == QChar::DirAN) { + eor = current; break; + } + case QChar::DirBN: + case QChar::DirB: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + if(status.eor == QChar::DirR) { + // neutrals go to R + eor = current - 1; + appendItems(analysis, sor, eor, control, dir); + dir = QChar::DirON; status.eor = QChar::DirEN; + dir = QChar::DirAN; + } + else if(status.eor == QChar::DirL || + (status.eor == QChar::DirEN && status.lastStrong == QChar::DirL)) { + eor = current; status.eor = dirCurrent; + } else { + // numbers on both sides, neutrals get right to left direction + if(dir != QChar::DirL) { + appendItems(analysis, sor, eor, control, dir); + dir = QChar::DirON; status.eor = QChar::DirON; + eor = current - 1; + dir = QChar::DirR; + appendItems(analysis, sor, eor, control, dir); + dir = QChar::DirON; status.eor = QChar::DirON; + dir = QChar::DirAN; + } else { + eor = current; status.eor = dirCurrent; + } + } + default: + break; + } + break; + } + case QChar::DirAN: + hasBidi = true; + dirCurrent = QChar::DirAN; + if(dir == QChar::DirON) dir = QChar::DirAN; + switch(status.last) + { + case QChar::DirL: + case QChar::DirAN: + eor = current; status.eor = QChar::DirAN; break; + case QChar::DirR: + case QChar::DirAL: + case QChar::DirEN: + if (eor >= 0){ + appendItems(analysis, sor, eor, control, dir); + } else { + eor = current; + } + dir = QChar::DirAN; status.eor = QChar::DirAN; + break; + case QChar::DirCS: + if(status.eor == QChar::DirAN) { + eor = current; break; + } + case QChar::DirES: + case QChar::DirET: + case QChar::DirBN: + case QChar::DirB: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + if(status.eor == QChar::DirR) { + // neutrals go to R + eor = current - 1; + appendItems(analysis, sor, eor, control, dir); + status.eor = QChar::DirAN; + dir = QChar::DirAN; + } else if(status.eor == QChar::DirL || + (status.eor == QChar::DirEN && status.lastStrong == QChar::DirL)) { + eor = current; status.eor = dirCurrent; + } else { + // numbers on both sides, neutrals get right to left direction + if(dir != QChar::DirL) { + appendItems(analysis, sor, eor, control, dir); + status.eor = QChar::DirON; + eor = current - 1; + dir = QChar::DirR; + appendItems(analysis, sor, eor, control, dir); + status.eor = QChar::DirAN; + dir = QChar::DirAN; + } else { + eor = current; status.eor = dirCurrent; + } + } + default: + break; + } + break; + case QChar::DirES: + case QChar::DirCS: + break; + case QChar::DirET: + if(status.last == QChar::DirEN) { + dirCurrent = QChar::DirEN; + eor = current; status.eor = dirCurrent; + } + break; + + // boundary neutrals should be ignored + case QChar::DirBN: + break; + // neutrals + case QChar::DirB: + // ### what do we do with newline and paragraph separators that come to here? + break; + case QChar::DirS: + // ### implement rule L1 + break; + case QChar::DirWS: + case QChar::DirON: + break; + default: + break; + } + + //qDebug() << " after: dir=" << // dir << " current=" << dirCurrent << " last=" << status.last << " eor=" << status.eor << " lastStrong=" << status.lastStrong << " embedding=" << control.direction(); + + if(current >= (int)length) break; + + // set status.last as needed. + switch(dirCurrent) { + case QChar::DirET: + case QChar::DirES: + case QChar::DirCS: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + switch(status.last) + { + case QChar::DirL: + case QChar::DirR: + case QChar::DirAL: + case QChar::DirEN: + case QChar::DirAN: + status.last = dirCurrent; + break; + default: + status.last = QChar::DirON; + } + break; + case QChar::DirNSM: + case QChar::DirBN: + // ignore these + break; + case QChar::DirLRO: + case QChar::DirLRE: + status.last = QChar::DirL; + break; + case QChar::DirRLO: + case QChar::DirRLE: + status.last = QChar::DirR; + break; + case QChar::DirEN: + if (status.last == QChar::DirL) { + status.last = QChar::DirL; + break; + } + // fall through + default: + status.last = dirCurrent; + } + + ++current; + } + +#if (BIDI_DEBUG >= 1) + qDebug() << "reached end of line current=" << current << ", eor=" << eor; +#endif + eor = current - 1; // remove dummy char + + if (sor <= eor) + appendItems(analysis, sor, eor, control, dir); + + return hasBidi; +} + +void QTextEngine::bidiReorder(int numItems, const quint8 *levels, int *visualOrder) +{ + + // first find highest and lowest levels + quint8 levelLow = 128; + quint8 levelHigh = 0; + int i = 0; + while (i < numItems) { + //printf("level = %d\n", r->level); + if (levels[i] > levelHigh) + levelHigh = levels[i]; + if (levels[i] < levelLow) + levelLow = levels[i]; + i++; + } + + // implements reordering of the line (L2 according to BiDi spec): + // L2. From the highest level found in the text to the lowest odd level on each line, + // reverse any contiguous sequence of characters that are at that level or higher. + + // reversing is only done up to the lowest odd level + if(!(levelLow%2)) levelLow++; + +#if (BIDI_DEBUG >= 1) +// qDebug() << "reorderLine: lineLow = " << (uint)levelLow << ", lineHigh = " << (uint)levelHigh; +#endif + + int count = numItems - 1; + for (i = 0; i < numItems; i++) + visualOrder[i] = i; + + while(levelHigh >= levelLow) { + int i = 0; + while (i < count) { + while(i < count && levels[i] < levelHigh) i++; + int start = i; + while(i <= count && levels[i] >= levelHigh) i++; + int end = i-1; + + if(start != end) { + //qDebug() << "reversing from " << start << " to " << end; + for(int j = 0; j < (end-start+1)/2; j++) { + int tmp = visualOrder[start+j]; + visualOrder[start+j] = visualOrder[end-j]; + visualOrder[end-j] = tmp; + } + } + i++; + } + levelHigh--; + } + +#if (BIDI_DEBUG >= 1) +// qDebug() << "visual order is:"; +// for (i = 0; i < numItems; i++) +// qDebug() << visualOrder[i]; +#endif +} + +QT_BEGIN_INCLUDE_NAMESPACE + +#if defined(Q_WS_X11) || defined (Q_WS_QWS) +# include "qfontengine_ft_p.h" +#elif defined(Q_WS_MAC) +# include "qtextengine_mac.cpp" +#endif + +#include + +QT_END_INCLUDE_NAMESPACE + +// ask the font engine to find out which glyphs (as an index in the specific font) to use for the text in one item. +static bool stringToGlyphs(HB_ShaperItem *item, QGlyphLayout *glyphs, QFontEngine *fontEngine) +{ + int nGlyphs = item->num_glyphs; + + QTextEngine::ShaperFlags shaperFlags(QTextEngine::GlyphIndicesOnly); + if (item->item.bidiLevel % 2) + shaperFlags |= QTextEngine::RightToLeft; + + bool result = fontEngine->stringToCMap(reinterpret_cast(item->string + item->item.pos), item->item.length, glyphs, &nGlyphs, shaperFlags); + item->num_glyphs = nGlyphs; + glyphs->numGlyphs = nGlyphs; + return result; +} + +// shape all the items that intersect with the line, taking tab widths into account to find out what text actually fits in the line. +void QTextEngine::shapeLine(const QScriptLine &line) +{ + QFixed x; + bool first = true; + const int end = findItem(line.from + line.length - 1); + int item = findItem(line.from); + if (item == -1) + return; + for (item = findItem(line.from); item <= end; ++item) { + QScriptItem &si = layoutData->items[item]; + if (si.analysis.flags == QScriptAnalysis::Tab) { + ensureSpace(1); + si.width = calculateTabWidth(item, x); + } else { + shape(item); + } + if (first && si.position != line.from) { // that means our x position has to be offset + QGlyphLayout glyphs = shapedGlyphs(&si); + Q_ASSERT(line.from > si.position); + for (int i = line.from - si.position - 1; i >= 0; i--) { + x -= glyphs.effectiveAdvance(i); + } + } + first = false; + + x += si.width; + } +} + +void QTextEngine::shapeText(int item) const +{ + Q_ASSERT(item < layoutData->items.size()); + QScriptItem &si = layoutData->items[item]; + + if (si.num_glyphs) + return; + +#if defined(Q_WS_MAC) + shapeTextMac(item); +#elif defined(Q_WS_WINCE) + shapeTextWithCE(item); +#else + shapeTextWithHarfbuzz(item); +#endif + + si.width = 0; + + if (!si.num_glyphs) + return; + QGlyphLayout glyphs = shapedGlyphs(&si); + + QFont font = this->font(si); + bool letterSpacingIsAbsolute = font.d->letterSpacingIsAbsolute; + QFixed letterSpacing = font.d->letterSpacing; + QFixed wordSpacing = font.d->wordSpacing; + + if (letterSpacingIsAbsolute && letterSpacing.value()) + letterSpacing *= font.d->dpi / qt_defaultDpiY(); + + if (letterSpacing != 0) { + for (int i = 1; i < si.num_glyphs; ++i) { + if (glyphs.attributes[i].clusterStart) { + if (letterSpacingIsAbsolute) + glyphs.advances_x[i-1] += letterSpacing; + else { + QFixed &advance = glyphs.advances_x[i-1]; + advance += (letterSpacing - 100) * advance / 100; + } + } + } + if (letterSpacingIsAbsolute) + glyphs.advances_x[si.num_glyphs-1] += letterSpacing; + else { + QFixed &advance = glyphs.advances_x[si.num_glyphs-1]; + advance += (letterSpacing - 100) * advance / 100; + } + } + if (wordSpacing != 0) { + for (int i = 0; i < si.num_glyphs; ++i) { + if (glyphs.attributes[i].justification == HB_Space + || glyphs.attributes[i].justification == HB_Arabic_Space) { + // word spacing only gets added once to a consecutive run of spaces (see CSS spec) + if (i + 1 == si.num_glyphs + ||(glyphs.attributes[i+1].justification != HB_Space + && glyphs.attributes[i+1].justification != HB_Arabic_Space)) + glyphs.advances_x[i] += wordSpacing; + } + } + } + + for (int i = 0; i < si.num_glyphs; ++i) + si.width += glyphs.advances_x[i]; +} + +static inline bool hasCaseChange(const QScriptItem &si) +{ + return si.analysis.flags == QScriptAnalysis::SmallCaps || + si.analysis.flags == QScriptAnalysis::Uppercase || + si.analysis.flags == QScriptAnalysis::Lowercase; +} + +#if defined(Q_WS_WINCE) //TODO +// set the glyph attributes heuristically. Assumes a 1 to 1 relationship between chars and glyphs +// and no reordering. +// also computes logClusters heuristically +static void heuristicSetGlyphAttributes(const QChar *uc, int length, QGlyphLayout *glyphs, unsigned short *logClusters, int num_glyphs) +{ + // ### zeroWidth and justification are missing here!!!!! + + Q_UNUSED(num_glyphs); + Q_ASSERT(num_glyphs <= length); + +// qDebug("QScriptEngine::heuristicSetGlyphAttributes, num_glyphs=%d", item->num_glyphs); + + int glyph_pos = 0; + for (int i = 0; i < length; i++) { + if (uc[i].unicode() >= 0xd800 && uc[i].unicode() < 0xdc00 && i < length-1 + && uc[i+1].unicode() >= 0xdc00 && uc[i+1].unicode() < 0xe000) { + logClusters[i] = glyph_pos; + logClusters[++i] = glyph_pos; + } else { + logClusters[i] = glyph_pos; + } + ++glyph_pos; + } + + // first char in a run is never (treated as) a mark + int cStart = 0; + + const bool symbolFont = false; // #### + glyphs->attributes[0].mark = false; + glyphs->attributes[0].clusterStart = true; + glyphs->attributes[0].dontPrint = (!symbolFont && uc[0].unicode() == 0x00ad) || qIsControlChar(uc[0].unicode()); + + int pos = 0; + int lastCat = QChar::category(uc[0].unicode()); + for (int i = 1; i < length; ++i) { + if (logClusters[i] == pos) + // same glyph + continue; + ++pos; + while (pos < logClusters[i]) { + glyphs[pos].attributes = glyphs[pos-1].attributes; + ++pos; + } + // hide soft-hyphens by default + if ((!symbolFont && uc[i].unicode() == 0x00ad) || qIsControlChar(uc[i].unicode())) + glyphs->attributes[pos].dontPrint = true; + const QUnicodeTables::Properties *prop = QUnicodeTables::properties(uc[i].unicode()); + int cat = prop->category; + if (cat != QChar::Mark_NonSpacing) { + glyphs->attributes[pos].mark = false; + glyphs->attributes[pos].clusterStart = true; + glyphs->attributes[pos].combiningClass = 0; + cStart = logClusters[i]; + } else { + int cmb = prop->combiningClass; + + if (cmb == 0) { + // Fix 0 combining classes + if ((uc[pos].unicode() & 0xff00) == 0x0e00) { + // thai or lao + unsigned char col = uc[pos].cell(); + if (col == 0x31 || + col == 0x34 || + col == 0x35 || + col == 0x36 || + col == 0x37 || + col == 0x47 || + col == 0x4c || + col == 0x4d || + col == 0x4e) { + cmb = QChar::Combining_AboveRight; + } else if (col == 0xb1 || + col == 0xb4 || + col == 0xb5 || + col == 0xb6 || + col == 0xb7 || + col == 0xbb || + col == 0xcc || + col == 0xcd) { + cmb = QChar::Combining_Above; + } else if (col == 0xbc) { + cmb = QChar::Combining_Below; + } + } + } + + glyphs->attributes[pos].mark = true; + glyphs->attributes[pos].clusterStart = false; + glyphs->attributes[pos].combiningClass = cmb; + logClusters[i] = cStart; + glyphs->advances_x[pos] = 0; + glyphs->advances_y[pos] = 0; + } + + // one gets an inter character justification point if the current char is not a non spacing mark. + // as then the current char belongs to the last one and one gets a space justification point + // after the space char. + if (lastCat == QChar::Separator_Space) + glyphs->attributes[pos-1].justification = HB_Space; + else if (cat != QChar::Mark_NonSpacing) + glyphs->attributes[pos-1].justification = HB_Character; + else + glyphs->attributes[pos-1].justification = HB_NoJustification; + + lastCat = cat; + } + pos = logClusters[length-1]; + if (lastCat == QChar::Separator_Space) + glyphs->attributes[pos].justification = HB_Space; + else + glyphs->attributes[pos].justification = HB_Character; +} + +void QTextEngine::shapeTextWithCE(int item) const +{ + QScriptItem &si = layoutData->items[item]; + si.glyph_data_offset = layoutData->used; + + QFontEngine *fe = fontEngine(si, &si.ascent, &si.descent, &si.leading); + + QTextEngine::ShaperFlags flags; + if (si.analysis.bidiLevel % 2) + flags |= RightToLeft; + if (option.useDesignMetrics()) + flags |= DesignMetrics; + + // pre-initialize char attributes + if (! attributes()) + return; + + const int len = length(item); + int num_glyphs = length(item); + const QChar *str = layoutData->string.unicode() + si.position; + ushort upperCased[256]; + if (hasCaseChange(si)) { + ushort *uc = upperCased; + if (len > 256) + uc = new ushort[len]; + for (int i = 0; i < len; ++i) { + if(si.analysis.flags == QScriptAnalysis::Lowercase) + uc[i] = str[i].toLower().unicode(); + else + uc[i] = str[i].toUpper().unicode(); + } + str = reinterpret_cast(uc); + } + + while (true) { + if (! ensureSpace(num_glyphs)) { + // If str is converted to uppercase/lowercase form with a new buffer, + // we need to delete that buffer before return for error + const ushort *uc = reinterpret_cast(str); + if (hasCaseChange(si) && uc != upperCased) + delete [] uc; + return; + } + num_glyphs = layoutData->glyphLayout.numGlyphs - layoutData->used; + + QGlyphLayout g = availableGlyphs(&si); + unsigned short *log_clusters = logClusters(&si); + + if (fe->stringToCMap(str, + len, + &g, + &num_glyphs, + flags)) { + heuristicSetGlyphAttributes(str, len, &g, log_clusters, num_glyphs); + break; + } + } + + si.num_glyphs = num_glyphs; + + layoutData->used += si.num_glyphs; + + const ushort *uc = reinterpret_cast(str); + if (hasCaseChange(si) && uc != upperCased) + delete [] uc; +} +#endif + +static inline void moveGlyphData(const QGlyphLayout &destination, const QGlyphLayout &source, int num) +{ + if (num > 0 && destination.glyphs != source.glyphs) { + memmove(destination.glyphs, source.glyphs, num * sizeof(HB_Glyph)); + memmove(destination.attributes, source.attributes, num * sizeof(HB_GlyphAttributes)); + memmove(destination.advances_x, source.advances_x, num * sizeof(HB_Fixed)); + memmove(destination.offsets, source.offsets, num * sizeof(HB_FixedPoint)); + } +} + +/// take the item from layoutData->items and +void QTextEngine::shapeTextWithHarfbuzz(int item) const +{ + Q_ASSERT(sizeof(HB_Fixed) == sizeof(QFixed)); + Q_ASSERT(sizeof(HB_FixedPoint) == sizeof(QFixedPoint)); + + QScriptItem &si = layoutData->items[item]; + + si.glyph_data_offset = layoutData->used; + + QFontEngine *font = fontEngine(si, &si.ascent, &si.descent, &si.leading); + + bool kerningEnabled = this->font(si).d->kerning; + + HB_ShaperItem entire_shaper_item; + qMemSet(&entire_shaper_item, 0, sizeof(entire_shaper_item)); + entire_shaper_item.string = reinterpret_cast(layoutData->string.constData()); + entire_shaper_item.stringLength = layoutData->string.length(); + entire_shaper_item.item.script = (HB_Script)si.analysis.script; + entire_shaper_item.item.pos = si.position; + entire_shaper_item.item.length = length(item); + entire_shaper_item.item.bidiLevel = si.analysis.bidiLevel; + + HB_UChar16 upperCased[256]; // XXX what about making this 4096, so we don't have to extend it ever. + if (hasCaseChange(si)) { + HB_UChar16 *uc = upperCased; + if (entire_shaper_item.item.length > 256) + uc = new HB_UChar16[entire_shaper_item.item.length]; + for (uint i = 0; i < entire_shaper_item.item.length; ++i) { + if(si.analysis.flags == QScriptAnalysis::Lowercase) + uc[i] = QChar::toLower(entire_shaper_item.string[si.position + i]); + else + uc[i] = QChar::toUpper(entire_shaper_item.string[si.position + i]); + } + entire_shaper_item.item.pos = 0; + entire_shaper_item.string = uc; + entire_shaper_item.stringLength = entire_shaper_item.item.length; + } + + entire_shaper_item.shaperFlags = 0; + if (!kerningEnabled) + entire_shaper_item.shaperFlags |= HB_ShaperFlag_NoKerning; + if (option.useDesignMetrics()) + entire_shaper_item.shaperFlags |= HB_ShaperFlag_UseDesignMetrics; + + entire_shaper_item.num_glyphs = qMax(layoutData->glyphLayout.numGlyphs - layoutData->used, int(entire_shaper_item.item.length)); + if (! ensureSpace(entire_shaper_item.num_glyphs)) { + if (hasCaseChange(si)) + delete [] const_cast(entire_shaper_item.string); + return; + } + QGlyphLayout initialGlyphs = availableGlyphs(&si).mid(0, entire_shaper_item.num_glyphs); + + if (!stringToGlyphs(&entire_shaper_item, &initialGlyphs, font)) { + if (! ensureSpace(entire_shaper_item.num_glyphs)) { + if (hasCaseChange(si)) + delete [] const_cast(entire_shaper_item.string); + return; + } + initialGlyphs = availableGlyphs(&si).mid(0, entire_shaper_item.num_glyphs); + + if (!stringToGlyphs(&entire_shaper_item, &initialGlyphs, font)) { + // ############ if this happens there's a bug in the fontengine + if (hasCaseChange(si) && entire_shaper_item.string != upperCased) + delete [] const_cast(entire_shaper_item.string); + return; + } + } + + // split up the item into parts that come from different font engines. + QVarLengthArray itemBoundaries(2); + // k * 2 entries, array[k] == index in string, array[k + 1] == index in glyphs + itemBoundaries[0] = entire_shaper_item.item.pos; + itemBoundaries[1] = 0; + + if (font->type() == QFontEngine::Multi) { + uint lastEngine = 0; + int charIdx = entire_shaper_item.item.pos; + const int stringEnd = charIdx + entire_shaper_item.item.length; + for (quint32 i = 0; i < entire_shaper_item.num_glyphs; ++i, ++charIdx) { + uint engineIdx = initialGlyphs.glyphs[i] >> 24; + if (engineIdx != lastEngine && i > 0) { + itemBoundaries.append(charIdx); + itemBoundaries.append(i); + } + lastEngine = engineIdx; + if (HB_IsHighSurrogate(entire_shaper_item.string[charIdx]) + && charIdx < stringEnd - 1 + && HB_IsLowSurrogate(entire_shaper_item.string[charIdx + 1])) + ++charIdx; + } + } + + + + int remaining_glyphs = entire_shaper_item.num_glyphs; + int glyph_pos = 0; + // for each item shape using harfbuzz and store the results in our layoutData's glyphs array. + for (int k = 0; k < itemBoundaries.size(); k += 2) { // for the +2, see the comment at the definition of itemBoundaries + + HB_ShaperItem shaper_item = entire_shaper_item; + + shaper_item.item.pos = itemBoundaries[k]; + if (k < itemBoundaries.size() - 3) { + shaper_item.item.length = itemBoundaries[k + 2] - shaper_item.item.pos; + shaper_item.num_glyphs = itemBoundaries[k + 3] - itemBoundaries[k + 1]; + } else { // last combo in the list, avoid out of bounds access. + shaper_item.item.length -= shaper_item.item.pos - entire_shaper_item.item.pos; + shaper_item.num_glyphs -= itemBoundaries[k + 1]; + } + shaper_item.initialGlyphCount = shaper_item.num_glyphs; + if (shaper_item.num_glyphs < shaper_item.item.length) + shaper_item.num_glyphs = shaper_item.item.length; + + QFontEngine *actualFontEngine = font; + uint engineIdx = 0; + if (font->type() == QFontEngine::Multi) { + engineIdx = uint(availableGlyphs(&si).glyphs[glyph_pos] >> 24); + + actualFontEngine = static_cast(font)->engine(engineIdx); + } + + shaper_item.font = actualFontEngine->harfbuzzFont(); + shaper_item.face = actualFontEngine->harfbuzzFace(); + + shaper_item.glyphIndicesPresent = true; + + remaining_glyphs -= shaper_item.initialGlyphCount; + + do { + if (! ensureSpace(glyph_pos + shaper_item.num_glyphs + remaining_glyphs)) { + if (hasCaseChange(si)) + delete [] const_cast(entire_shaper_item.string); + return; + } + + const QGlyphLayout g = availableGlyphs(&si).mid(glyph_pos); + if (shaper_item.num_glyphs > shaper_item.item.length) + moveGlyphData(g.mid(shaper_item.num_glyphs), g.mid(shaper_item.initialGlyphCount), remaining_glyphs); + + shaper_item.glyphs = g.glyphs; + shaper_item.attributes = g.attributes; + shaper_item.advances = reinterpret_cast(g.advances_x); + shaper_item.offsets = reinterpret_cast(g.offsets); + + if (shaper_item.glyphIndicesPresent) { + for (hb_uint32 i = 0; i < shaper_item.initialGlyphCount; ++i) + shaper_item.glyphs[i] &= 0x00ffffff; + } + + shaper_item.log_clusters = logClusters(&si) + shaper_item.item.pos - entire_shaper_item.item.pos; + +// qDebug(" .. num_glyphs=%d, used=%d, item.num_glyphs=%d", num_glyphs, used, shaper_item.num_glyphs); + } while (!qShapeItem(&shaper_item)); // this does the actual shaping via harfbuzz. + + QGlyphLayout g = availableGlyphs(&si).mid(glyph_pos, shaper_item.num_glyphs); + moveGlyphData(g.mid(shaper_item.num_glyphs), g.mid(shaper_item.initialGlyphCount), remaining_glyphs); + + for (hb_uint32 i = 0; i < shaper_item.num_glyphs; ++i) + g.glyphs[i] = g.glyphs[i] | (engineIdx << 24); + + for (hb_uint32 i = 0; i < shaper_item.item.length; ++i) + shaper_item.log_clusters[i] += glyph_pos; + + if (kerningEnabled && !shaper_item.kerning_applied) + font->doKerning(&g, option.useDesignMetrics() ? QFlag(QTextEngine::DesignMetrics) : QFlag(0)); + + glyph_pos += shaper_item.num_glyphs; + } + +// qDebug(" -> item: script=%d num_glyphs=%d", shaper_item.script, shaper_item.num_glyphs); + si.num_glyphs = glyph_pos; + + layoutData->used += si.num_glyphs; + + if (hasCaseChange(si) && entire_shaper_item.string != upperCased) + delete [] const_cast(entire_shaper_item.string); +} + +static void init(QTextEngine *e) +{ + e->ignoreBidi = false; + e->cacheGlyphs = false; + e->forceJustification = false; + + e->layoutData = 0; + + e->minWidth = 0; + e->maxWidth = 0; + + e->underlinePositions = 0; + e->specialData = 0; + e->stackEngine = false; +} + +QTextEngine::QTextEngine() +{ + init(this); +} + +QTextEngine::QTextEngine(const QString &str, const QFont &f) + : text(str), + fnt(f) +{ + init(this); +} + +QTextEngine::~QTextEngine() +{ + if (!stackEngine) + delete layoutData; + delete specialData; +} + +const HB_CharAttributes *QTextEngine::attributes() const +{ + if (layoutData && layoutData->haveCharAttributes) + return (HB_CharAttributes *) layoutData->memory; + + itemize(); + if (! ensureSpace(layoutData->string.length())) + return NULL; + + QVarLengthArray hbScriptItems(layoutData->items.size()); + + for (int i = 0; i < layoutData->items.size(); ++i) { + const QScriptItem &si = layoutData->items[i]; + hbScriptItems[i].pos = si.position; + hbScriptItems[i].length = length(i); + hbScriptItems[i].bidiLevel = si.analysis.bidiLevel; + hbScriptItems[i].script = (HB_Script)si.analysis.script; + } + + qGetCharAttributes(reinterpret_cast(layoutData->string.constData()), + layoutData->string.length(), + hbScriptItems.data(), hbScriptItems.size(), + (HB_CharAttributes *)layoutData->memory); + + + layoutData->haveCharAttributes = true; + return (HB_CharAttributes *) layoutData->memory; +} + +void QTextEngine::shape(int item) const +{ + if (layoutData->items[item].analysis.flags == QScriptAnalysis::Object) { + ensureSpace(1); + if (block.docHandle()) { + QTextFormat format = formats()->format(formatIndex(&layoutData->items[item])); + docLayout()->resizeInlineObject(QTextInlineObject(item, const_cast(this)), + layoutData->items[item].position + block.position(), format); + } + } else if (layoutData->items[item].analysis.flags == QScriptAnalysis::Tab) { + // set up at least the ascent/descent/leading of the script item for the tab + fontEngine(layoutData->items[item], + &layoutData->items[item].ascent, + &layoutData->items[item].descent, + &layoutData->items[item].leading); + } else { + shapeText(item); + } +} + +static inline void releaseCachedFontEngine(QFontEngine *fontEngine) +{ + if (fontEngine) { + fontEngine->ref.deref(); + if (fontEngine->cache_count == 0 && fontEngine->ref == 0) + delete fontEngine; + } +} + +void QTextEngine::invalidate() +{ + freeMemory(); + minWidth = 0; + maxWidth = 0; + if (specialData) + specialData->resolvedFormatIndices.clear(); + + releaseCachedFontEngine(feCache.prevFontEngine); + releaseCachedFontEngine(feCache.prevScaledFontEngine); + feCache.reset(); +} + +void QTextEngine::clearLineData() +{ + lines.clear(); +} + +void QTextEngine::validate() const +{ + if (layoutData) + return; + layoutData = new LayoutData(); + if (block.docHandle()) { + layoutData->string = block.text(); + if (option.flags() & QTextOption::ShowLineAndParagraphSeparators) + layoutData->string += QLatin1Char(block.next().isValid() ? 0xb6 : 0x20); + } else { + layoutData->string = text; + } + if (specialData && specialData->preeditPosition != -1) + layoutData->string.insert(specialData->preeditPosition, specialData->preeditText); +} + +void QTextEngine::itemize() const +{ + validate(); + if (layoutData->items.size()) + return; + + int length = layoutData->string.length(); + if (!length) + return; +#if defined(Q_WS_MAC) && !defined(QT_MAC_USE_COCOA) + // ATSUI requires RTL flags to correctly identify the character stops. + bool ignore = false; +#else + bool ignore = ignoreBidi; +#endif + + bool rtl = isRightToLeft(); + + if (!ignore && !rtl) { + ignore = true; + const QChar *start = layoutData->string.unicode(); + const QChar * const end = start + length; + while (start < end) { + if (start->unicode() >= 0x590) { + ignore = false; + break; + } + ++start; + } + } + + QVarLengthArray scriptAnalysis(length); + QScriptAnalysis *analysis = scriptAnalysis.data(); + + QBidiControl control(rtl); + + if (ignore) { + memset(analysis, 0, length*sizeof(QScriptAnalysis)); + if (option.textDirection() == Qt::RightToLeft) { + for (int i = 0; i < length; ++i) + analysis[i].bidiLevel = 1; + layoutData->hasBidi = true; + } + } else { + layoutData->hasBidi = bidiItemize(const_cast(this), analysis, control); + } + + const ushort *uc = reinterpret_cast(layoutData->string.unicode()); + const ushort *e = uc + length; + int lastScript = QUnicodeTables::Common; + while (uc < e) { + int script = QUnicodeTables::script(*uc); + if (script == QUnicodeTables::Inherited) + script = lastScript; + analysis->flags = QScriptAnalysis::None; + if (*uc == QChar::ObjectReplacementCharacter) { + if (analysis->bidiLevel % 2) + --analysis->bidiLevel; + analysis->script = QUnicodeTables::Common; + analysis->flags = QScriptAnalysis::Object; + } else if (*uc == QChar::LineSeparator) { + if (analysis->bidiLevel % 2) + --analysis->bidiLevel; + analysis->script = QUnicodeTables::Common; + analysis->flags = QScriptAnalysis::LineOrParagraphSeparator; + if (option.flags() & QTextOption::ShowLineAndParagraphSeparators) + *const_cast(uc) = 0x21B5; // visual line separator + } else if (*uc == 9) { + analysis->script = QUnicodeTables::Common; + analysis->flags = QScriptAnalysis::Tab; + analysis->bidiLevel = control.baseLevel(); + } else if ((*uc == 32 || *uc == QChar::Nbsp) + && (option.flags() & QTextOption::ShowTabsAndSpaces)) { + analysis->script = QUnicodeTables::Common; + analysis->flags = QScriptAnalysis::Space; + analysis->bidiLevel = control.baseLevel(); + } else { + analysis->script = script; + } + lastScript = analysis->script; + ++uc; + ++analysis; + } + if (option.flags() & QTextOption::ShowLineAndParagraphSeparators) { + (analysis-1)->flags = QScriptAnalysis::LineOrParagraphSeparator; // to exclude it from width + } + + Itemizer itemizer(layoutData->string, scriptAnalysis.data(), layoutData->items); + + const QTextDocumentPrivate *p = block.docHandle(); + if (p) { + SpecialData *s = specialData; + + QTextDocumentPrivate::FragmentIterator it = p->find(block.position()); + QTextDocumentPrivate::FragmentIterator end = p->find(block.position() + block.length() - 1); // -1 to omit the block separator char + int format = it.value()->format; + + int prevPosition = 0; + int position = prevPosition; + while (1) { + const QTextFragmentData * const frag = it.value(); + if (it == end || format != frag->format) { + if (s && position >= s->preeditPosition) { + position += s->preeditText.length(); + s = 0; + } + Q_ASSERT(position <= length); + itemizer.generate(prevPosition, position - prevPosition, + formats()->charFormat(format).fontCapitalization()); + if (it == end) { + if (position < length) + itemizer.generate(position, length - position, + formats()->charFormat(format).fontCapitalization()); + break; + } + format = frag->format; + prevPosition = position; + } + position += frag->size_array[0]; + ++it; + } + } else { + itemizer.generate(0, length, static_cast (fnt.d->capital)); + } + + addRequiredBoundaries(); + resolveAdditionalFormats(); +} + +bool QTextEngine::isRightToLeft() const +{ + switch (option.textDirection()) { + case Qt::LeftToRight: + return false; + case Qt::RightToLeft: + return true; + default: + break; + } + // this places the cursor in the right position depending on the keyboard layout + if (layoutData->string.isEmpty()) + return QApplication::keyboardInputDirection() == Qt::RightToLeft; + return layoutData->string.isRightToLeft(); +} + + +int QTextEngine::findItem(int strPos) const +{ + itemize(); + int left = 0; + int right = layoutData->items.size()-1; + while(left <= right) { + int middle = ((right-left)/2)+left; + if (strPos > layoutData->items[middle].position) + left = middle+1; + else if(strPos < layoutData->items[middle].position) + right = middle-1; + else { + return middle; + } + } + return right; +} + +QFixed QTextEngine::width(int from, int len) const +{ + itemize(); + + QFixed w = 0; + +// qDebug("QTextEngine::width(from = %d, len = %d), numItems=%d, strleng=%d", from, len, items.size(), string.length()); + for (int i = 0; i < layoutData->items.size(); i++) { + const QScriptItem *si = layoutData->items.constData() + i; + int pos = si->position; + int ilen = length(i); +// qDebug("item %d: from %d len %d", i, pos, ilen); + if (pos >= from + len) + break; + if (pos + ilen > from) { + if (!si->num_glyphs) + shape(i); + + if (si->analysis.flags == QScriptAnalysis::Object) { + w += si->width; + continue; + } else if (si->analysis.flags == QScriptAnalysis::Tab) { + w += calculateTabWidth(i, w); + continue; + } + + + QGlyphLayout glyphs = shapedGlyphs(si); + unsigned short *logClusters = this->logClusters(si); + +// fprintf(stderr, " logclusters:"); +// for (int k = 0; k < ilen; k++) +// fprintf(stderr, " %d", logClusters[k]); +// fprintf(stderr, "\n"); + // do the simple thing for now and give the first glyph in a cluster the full width, all other ones 0. + int charFrom = from - pos; + if (charFrom < 0) + charFrom = 0; + int glyphStart = logClusters[charFrom]; + if (charFrom > 0 && logClusters[charFrom-1] == glyphStart) + while (charFrom < ilen && logClusters[charFrom] == glyphStart) + charFrom++; + if (charFrom < ilen) { + glyphStart = logClusters[charFrom]; + int charEnd = from + len - 1 - pos; + if (charEnd >= ilen) + charEnd = ilen-1; + int glyphEnd = logClusters[charEnd]; + while (charEnd < ilen && logClusters[charEnd] == glyphEnd) + charEnd++; + glyphEnd = (charEnd == ilen) ? si->num_glyphs : logClusters[charEnd]; + +// qDebug("char: start=%d end=%d / glyph: start = %d, end = %d", charFrom, charEnd, glyphStart, glyphEnd); + for (int i = glyphStart; i < glyphEnd; i++) + w += glyphs.advances_x[i] * !glyphs.attributes[i].dontPrint; + } + } + } +// qDebug(" --> w= %d ", w); + return w; +} + +glyph_metrics_t QTextEngine::boundingBox(int from, int len) const +{ + itemize(); + + glyph_metrics_t gm; + + for (int i = 0; i < layoutData->items.size(); i++) { + const QScriptItem *si = layoutData->items.constData() + i; + + int pos = si->position; + int ilen = length(i); + if (pos > from + len) + break; + if (pos + ilen > from) { + if (!si->num_glyphs) + shape(i); + + if (si->analysis.flags == QScriptAnalysis::Object) { + gm.width += si->width; + continue; + } else if (si->analysis.flags == QScriptAnalysis::Tab) { + gm.width += calculateTabWidth(i, gm.width); + continue; + } + + unsigned short *logClusters = this->logClusters(si); + QGlyphLayout glyphs = shapedGlyphs(si); + + // do the simple thing for now and give the first glyph in a cluster the full width, all other ones 0. + int charFrom = from - pos; + if (charFrom < 0) + charFrom = 0; + int glyphStart = logClusters[charFrom]; + if (charFrom > 0 && logClusters[charFrom-1] == glyphStart) + while (charFrom < ilen && logClusters[charFrom] == glyphStart) + charFrom++; + if (charFrom < ilen) { + QFontEngine *fe = fontEngine(*si); + glyphStart = logClusters[charFrom]; + int charEnd = from + len - 1 - pos; + if (charEnd >= ilen) + charEnd = ilen-1; + int glyphEnd = logClusters[charEnd]; + while (charEnd < ilen && logClusters[charEnd] == glyphEnd) + charEnd++; + glyphEnd = (charEnd == ilen) ? si->num_glyphs : logClusters[charEnd]; + if (glyphStart <= glyphEnd ) { + glyph_metrics_t m = fe->boundingBox(glyphs.mid(glyphStart, glyphEnd - glyphStart)); + gm.x = qMin(gm.x, m.x + gm.xoff); + gm.y = qMin(gm.y, m.y + gm.yoff); + gm.width = qMax(gm.width, m.width+gm.xoff); + gm.height = qMax(gm.height, m.height+gm.yoff); + gm.xoff += m.xoff; + gm.yoff += m.yoff; + } + } + } + } + return gm; +} + +glyph_metrics_t QTextEngine::tightBoundingBox(int from, int len) const +{ + itemize(); + + glyph_metrics_t gm; + + for (int i = 0; i < layoutData->items.size(); i++) { + const QScriptItem *si = layoutData->items.constData() + i; + int pos = si->position; + int ilen = length(i); + if (pos > from + len) + break; + if (pos + len > from) { + if (!si->num_glyphs) + shape(i); + unsigned short *logClusters = this->logClusters(si); + QGlyphLayout glyphs = shapedGlyphs(si); + + // do the simple thing for now and give the first glyph in a cluster the full width, all other ones 0. + int charFrom = from - pos; + if (charFrom < 0) + charFrom = 0; + int glyphStart = logClusters[charFrom]; + if (charFrom > 0 && logClusters[charFrom-1] == glyphStart) + while (charFrom < ilen && logClusters[charFrom] == glyphStart) + charFrom++; + if (charFrom < ilen) { + glyphStart = logClusters[charFrom]; + int charEnd = from + len - 1 - pos; + if (charEnd >= ilen) + charEnd = ilen-1; + int glyphEnd = logClusters[charEnd]; + while (charEnd < ilen && logClusters[charEnd] == glyphEnd) + charEnd++; + glyphEnd = (charEnd == ilen) ? si->num_glyphs : logClusters[charEnd]; + if (glyphStart <= glyphEnd ) { + QFontEngine *fe = fontEngine(*si); + glyph_metrics_t m = fe->tightBoundingBox(glyphs.mid(glyphStart, glyphEnd - glyphStart)); + gm.x = qMin(gm.x, m.x + gm.xoff); + gm.y = qMin(gm.y, m.y + gm.yoff); + gm.width = qMax(gm.width, m.width+gm.xoff); + gm.height = qMax(gm.height, m.height+gm.yoff); + gm.xoff += m.xoff; + gm.yoff += m.yoff; + } + } + } + } + return gm; +} + +QFont QTextEngine::font(const QScriptItem &si) const +{ + QFont font = fnt; + if (hasFormats()) { + QTextCharFormat f = format(&si); + font = f.font(); + + if (block.docHandle() && block.docHandle()->layout()) { + // Make sure we get the right dpi on printers + QPaintDevice *pdev = block.docHandle()->layout()->paintDevice(); + if (pdev) + font = QFont(font, pdev); + } else { + font = font.resolve(fnt); + } + QTextCharFormat::VerticalAlignment valign = f.verticalAlignment(); + if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) { + if (font.pointSize() != -1) + font.setPointSize((font.pointSize() * 2) / 3); + else + font.setPixelSize((font.pixelSize() * 2) / 3); + } + } + + if (si.analysis.flags == QScriptAnalysis::SmallCaps) + font = font.d->smallCapsFont(); + + return font; +} + +QTextEngine::FontEngineCache::FontEngineCache() +{ + reset(); +} + +//we cache the previous results of this function, as calling it numerous times with the same effective +//input is common (and hard to cache at a higher level) +QFontEngine *QTextEngine::fontEngine(const QScriptItem &si, QFixed *ascent, QFixed *descent, QFixed *leading) const +{ + QFontEngine *engine = 0; + QFontEngine *scaledEngine = 0; + int script = si.analysis.script; + + QFont font = fnt; + if (hasFormats()) { + if (feCache.prevFontEngine && feCache.prevPosition == si.position && feCache.prevLength == length(&si) && feCache.prevScript == script) { + engine = feCache.prevFontEngine; + scaledEngine = feCache.prevScaledFontEngine; + } else { + QTextCharFormat f = format(&si); + font = f.font(); + + if (block.docHandle() && block.docHandle()->layout()) { + // Make sure we get the right dpi on printers + QPaintDevice *pdev = block.docHandle()->layout()->paintDevice(); + if (pdev) + font = QFont(font, pdev); + } else { + font = font.resolve(fnt); + } + engine = font.d->engineForScript(script); + QTextCharFormat::VerticalAlignment valign = f.verticalAlignment(); + if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) { + if (font.pointSize() != -1) + font.setPointSize((font.pointSize() * 2) / 3); + else + font.setPixelSize((font.pixelSize() * 2) / 3); + scaledEngine = font.d->engineForScript(script); + } + feCache.prevFontEngine = engine; + if (engine) + engine->ref.ref(); + feCache.prevScaledFontEngine = scaledEngine; + if (scaledEngine) + scaledEngine->ref.ref(); + feCache.prevScript = script; + feCache.prevPosition = si.position; + feCache.prevLength = length(&si); + } + } else { + if (feCache.prevFontEngine && feCache.prevScript == script && feCache.prevPosition == -1) + engine = feCache.prevFontEngine; + else { + engine = font.d->engineForScript(script); + feCache.prevFontEngine = engine; + if (engine) + engine->ref.ref(); + feCache.prevScript = script; + feCache.prevPosition = -1; + feCache.prevLength = -1; + feCache.prevScaledFontEngine = 0; + } + } + + if (si.analysis.flags == QScriptAnalysis::SmallCaps) { + QFontPrivate *p = font.d->smallCapsFontPrivate(); + scaledEngine = p->engineForScript(script); + } + + if (ascent) { + *ascent = engine->ascent(); + *descent = engine->descent(); + *leading = engine->leading(); + } + + if (scaledEngine) + return scaledEngine; + return engine; +} + +struct QJustificationPoint { + int type; + QFixed kashidaWidth; + QGlyphLayout glyph; + QFontEngine *fontEngine; +}; + +Q_DECLARE_TYPEINFO(QJustificationPoint, Q_PRIMITIVE_TYPE); + +static void set(QJustificationPoint *point, int type, const QGlyphLayout &glyph, QFontEngine *fe) +{ + point->type = type; + point->glyph = glyph; + point->fontEngine = fe; + + if (type >= HB_Arabic_Normal) { + QChar ch(0x640); // Kashida character + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + fe->stringToCMap(&ch, 1, &glyphs, &nglyphs, 0); + if (glyphs.glyphs[0] && glyphs.advances_x[0] != 0) { + point->kashidaWidth = glyphs.advances_x[0]; + } else { + point->type = HB_NoJustification; + point->kashidaWidth = 0; + } + } +} + + +void QTextEngine::justify(const QScriptLine &line) +{ +// qDebug("justify: line.gridfitted = %d, line.justified=%d", line.gridfitted, line.justified); + if (line.gridfitted && line.justified) + return; + + if (!line.gridfitted) { + // redo layout in device metrics, then adjust + const_cast(line).gridfitted = true; + } + + if ((option.alignment() & Qt::AlignHorizontal_Mask) != Qt::AlignJustify) + return; + + itemize(); + + if (!forceJustification) { + int end = line.from + (int)line.length; + if (end == layoutData->string.length()) + return; // no justification at end of paragraph + if (end && layoutData->items[findItem(end-1)].analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) + return; // no justification at the end of an explicitly separated line + } + + // justify line + int maxJustify = 0; + + // don't include trailing white spaces when doing justification + int line_length = line.length; + const HB_CharAttributes *a = attributes(); + if (! a) + return; + a += line.from; + while (line_length && a[line_length-1].whiteSpace) + --line_length; + // subtract one char more, as we can't justfy after the last character + --line_length; + + if (!line_length) + return; + + int firstItem = findItem(line.from); + int nItems = findItem(line.from + line_length - 1) - firstItem + 1; + + QVarLengthArray justificationPoints; + int nPoints = 0; +// qDebug("justifying from %d len %d, firstItem=%d, nItems=%d (%s)", line.from, line_length, firstItem, nItems, layoutData->string.mid(line.from, line_length).toUtf8().constData()); + QFixed minKashida = 0x100000; + + // we need to do all shaping before we go into the next loop, as we there + // store pointers to the glyph data that could get reallocated by the shaping + // process. + for (int i = 0; i < nItems; ++i) { + QScriptItem &si = layoutData->items[firstItem + i]; + if (!si.num_glyphs) + shape(firstItem + i); + } + + for (int i = 0; i < nItems; ++i) { + QScriptItem &si = layoutData->items[firstItem + i]; + + int kashida_type = HB_Arabic_Normal; + int kashida_pos = -1; + + int start = qMax(line.from - si.position, 0); + int end = qMin(line.from + line_length - (int)si.position, length(firstItem+i)); + + unsigned short *log_clusters = logClusters(&si); + + int gs = log_clusters[start]; + int ge = (end == length(firstItem+i) ? si.num_glyphs : log_clusters[end]); + + const QGlyphLayout g = shapedGlyphs(&si); + + for (int i = gs; i < ge; ++i) { + g.justifications[i].type = QGlyphJustification::JustifyNone; + g.justifications[i].nKashidas = 0; + g.justifications[i].space_18d6 = 0; + + justificationPoints.resize(nPoints+3); + int justification = g.attributes[i].justification; + + switch(justification) { + case HB_NoJustification: + break; + case HB_Space : + // fall through + case HB_Arabic_Space : + if (kashida_pos >= 0) { +// qDebug("kashida position at %d in word", kashida_pos); + set(&justificationPoints[nPoints], kashida_type, g.mid(kashida_pos), fontEngine(si)); + if (justificationPoints[nPoints].kashidaWidth > 0) { + minKashida = qMin(minKashida, justificationPoints[nPoints].kashidaWidth); + maxJustify = qMax(maxJustify, justificationPoints[nPoints].type); + ++nPoints; + } + } + kashida_pos = -1; + kashida_type = HB_Arabic_Normal; + // fall through + case HB_Character : + set(&justificationPoints[nPoints++], justification, g.mid(i), fontEngine(si)); + maxJustify = qMax(maxJustify, justification); + break; + case HB_Arabic_Normal : + case HB_Arabic_Waw : + case HB_Arabic_BaRa : + case HB_Arabic_Alef : + case HB_Arabic_HaaDal : + case HB_Arabic_Seen : + case HB_Arabic_Kashida : + if (justification >= kashida_type) { + kashida_pos = i; + kashida_type = justification; + } + } + } + if (kashida_pos >= 0) { + set(&justificationPoints[nPoints], kashida_type, g.mid(kashida_pos), fontEngine(si)); + if (justificationPoints[nPoints].kashidaWidth > 0) { + minKashida = qMin(minKashida, justificationPoints[nPoints].kashidaWidth); + maxJustify = qMax(maxJustify, justificationPoints[nPoints].type); + ++nPoints; + } + } + } + + QFixed need = line.width - line.textWidth; + if (need < 0) { + // line overflows already! + const_cast(line).justified = true; + return; + } + +// qDebug("doing justification: textWidth=%x, requested=%x, maxJustify=%d", line.textWidth.value(), line.width.value(), maxJustify); +// qDebug(" minKashida=%f, need=%f", minKashida.toReal(), need.toReal()); + + // distribute in priority order + if (maxJustify >= HB_Arabic_Normal) { + while (need >= minKashida) { + for (int type = maxJustify; need >= minKashida && type >= HB_Arabic_Normal; --type) { + for (int i = 0; need >= minKashida && i < nPoints; ++i) { + if (justificationPoints[i].type == type && justificationPoints[i].kashidaWidth <= need) { + justificationPoints[i].glyph.justifications->nKashidas++; + // ############ + justificationPoints[i].glyph.justifications->space_18d6 += justificationPoints[i].kashidaWidth.value(); + need -= justificationPoints[i].kashidaWidth; +// qDebug("adding kashida type %d with width %x, neednow %x", type, justificationPoints[i].kashidaWidth, need.value()); + } + } + } + } + } + Q_ASSERT(need >= 0); + if (!need) + goto end; + + maxJustify = qMin(maxJustify, (int)HB_Space); + for (int type = maxJustify; need != 0 && type > 0; --type) { + int n = 0; + for (int i = 0; i < nPoints; ++i) { + if (justificationPoints[i].type == type) + ++n; + } +// qDebug("number of points for justification type %d: %d", type, n); + + + if (!n) + continue; + + for (int i = 0; i < nPoints; ++i) { + if (justificationPoints[i].type == type) { + QFixed add = need/n; +// qDebug("adding %x to glyph %x", add.value(), justificationPoints[i].glyph->glyph); + justificationPoints[i].glyph.justifications[0].space_18d6 = add.value(); + need -= add; + --n; + } + } + + Q_ASSERT(!need); + } + end: + const_cast(line).justified = true; +} + +void QScriptLine::setDefaultHeight(QTextEngine *eng) +{ + QFont f; + QFontEngine *e; + + if (eng->block.docHandle() && eng->block.docHandle()->layout()) { + f = eng->block.charFormat().font(); + // Make sure we get the right dpi on printers + QPaintDevice *pdev = eng->block.docHandle()->layout()->paintDevice(); + if (pdev) + f = QFont(f, pdev); + e = f.d->engineForScript(QUnicodeTables::Common); + } else { + e = eng->fnt.d->engineForScript(QUnicodeTables::Common); + } + + QFixed other_ascent = e->ascent(); + QFixed other_descent = e->descent(); + QFixed other_leading = e->leading(); + leading = qMax(leading + ascent, other_leading + other_ascent) - qMax(ascent, other_ascent); + ascent = qMax(ascent, other_ascent); + descent = qMax(descent, other_descent); +} + +QTextEngine::LayoutData::LayoutData() +{ + memory = 0; + allocated = 0; + memory_on_stack = false; + used = 0; + hasBidi = false; + layoutState = LayoutEmpty; + haveCharAttributes = false; + logClustersPtr = 0; + available_glyphs = 0; +} + +QTextEngine::LayoutData::LayoutData(const QString &str, void **stack_memory, int _allocated) + : string(str) +{ + allocated = _allocated; + + int space_charAttributes = sizeof(HB_CharAttributes)*string.length()/sizeof(void*) + 1; + int space_logClusters = sizeof(unsigned short)*string.length()/sizeof(void*) + 1; + available_glyphs = ((int)allocated - space_charAttributes - space_logClusters)*(int)sizeof(void*)/(int)QGlyphLayout::spaceNeededForGlyphLayout(1); + + if (available_glyphs < str.length()) { + // need to allocate on the heap + allocated = 0; + + memory_on_stack = false; + memory = 0; + logClustersPtr = 0; + } else { + memory_on_stack = true; + memory = stack_memory; + logClustersPtr = (unsigned short *)(memory + space_charAttributes); + + void *m = memory + space_charAttributes + space_logClusters; + glyphLayout = QGlyphLayout(reinterpret_cast(m), str.length()); + glyphLayout.clear(); + memset(memory, 0, space_charAttributes*sizeof(void *)); + } + used = 0; + hasBidi = false; + layoutState = LayoutEmpty; + haveCharAttributes = false; +} + +QTextEngine::LayoutData::~LayoutData() +{ + if (!memory_on_stack) + free(memory); + memory = 0; +} + +bool QTextEngine::LayoutData::reallocate(int totalGlyphs) +{ + Q_ASSERT(totalGlyphs >= glyphLayout.numGlyphs); + if (memory_on_stack && available_glyphs >= totalGlyphs) { + glyphLayout.grow(glyphLayout.data(), totalGlyphs); + return true; + } + + int space_charAttributes = sizeof(HB_CharAttributes)*string.length()/sizeof(void*) + 1; + int space_logClusters = sizeof(unsigned short)*string.length()/sizeof(void*) + 1; + int space_glyphs = QGlyphLayout::spaceNeededForGlyphLayout(totalGlyphs)/sizeof(void*) + 2; + + int newAllocated = space_charAttributes + space_glyphs + space_logClusters; + // These values can be negative if the length of string/glyphs causes overflow, + // we can't layout such a long string all at once, so return false here to + // indicate there is a failure + if (space_charAttributes < 0 || space_logClusters < 0 || space_glyphs < 0 || newAllocated < allocated) { + layoutState = LayoutFailed; + return false; + } + + void **newMem = memory; + newMem = (void **)::realloc(memory_on_stack ? 0 : memory, newAllocated*sizeof(void *)); + if (!newMem) { + layoutState = LayoutFailed; + return false; + } + if (memory_on_stack) + memcpy(newMem, memory, allocated*sizeof(void *)); + memory = newMem; + memory_on_stack = false; + + void **m = memory; + m += space_charAttributes; + logClustersPtr = (unsigned short *) m; + m += space_logClusters; + + const int space_preGlyphLayout = space_charAttributes + space_logClusters; + if (allocated < space_preGlyphLayout) + memset(memory + allocated, 0, (space_preGlyphLayout - allocated)*sizeof(void *)); + + glyphLayout.grow(reinterpret_cast(m), totalGlyphs); + + allocated = newAllocated; + return true; +} + +// grow to the new size, copying the existing data to the new layout +void QGlyphLayout::grow(char *address, int totalGlyphs) +{ + QGlyphLayout oldLayout(address, numGlyphs); + QGlyphLayout newLayout(address, totalGlyphs); + + if (numGlyphs) { + // move the existing data + memmove(newLayout.attributes, oldLayout.attributes, numGlyphs * sizeof(HB_GlyphAttributes)); + memmove(newLayout.justifications, oldLayout.justifications, numGlyphs * sizeof(QGlyphJustification)); + memmove(newLayout.advances_y, oldLayout.advances_y, numGlyphs * sizeof(QFixed)); + memmove(newLayout.advances_x, oldLayout.advances_x, numGlyphs * sizeof(QFixed)); + memmove(newLayout.glyphs, oldLayout.glyphs, numGlyphs * sizeof(HB_Glyph)); + } + + // clear the new data + newLayout.clear(numGlyphs); + + *this = newLayout; +} + +void QTextEngine::freeMemory() +{ + if (!stackEngine) { + delete layoutData; + layoutData = 0; + } else { + layoutData->used = 0; + layoutData->hasBidi = false; + layoutData->layoutState = LayoutEmpty; + layoutData->haveCharAttributes = false; + } + for (int i = 0; i < lines.size(); ++i) { + lines[i].justified = 0; + lines[i].gridfitted = 0; + } +} + +int QTextEngine::formatIndex(const QScriptItem *si) const +{ + if (specialData && !specialData->resolvedFormatIndices.isEmpty()) + return specialData->resolvedFormatIndices.at(si - &layoutData->items[0]); + QTextDocumentPrivate *p = block.docHandle(); + if (!p) + return -1; + int pos = si->position; + if (specialData && si->position >= specialData->preeditPosition) { + if (si->position < specialData->preeditPosition + specialData->preeditText.length()) + pos = qMax(specialData->preeditPosition - 1, 0); + else + pos -= specialData->preeditText.length(); + } + QTextDocumentPrivate::FragmentIterator it = p->find(block.position() + pos); + return it.value()->format; +} + + +QTextCharFormat QTextEngine::format(const QScriptItem *si) const +{ + QTextCharFormat format; + const QTextFormatCollection *formats = 0; + if (block.docHandle()) { + formats = this->formats(); + format = formats->charFormat(formatIndex(si)); + } + if (specialData && specialData->resolvedFormatIndices.isEmpty()) { + int end = si->position + length(si); + for (int i = 0; i < specialData->addFormats.size(); ++i) { + const QTextLayout::FormatRange &r = specialData->addFormats.at(i); + if (r.start <= si->position && r.start + r.length >= end) { + if (!specialData->addFormatIndices.isEmpty()) + format.merge(formats->format(specialData->addFormatIndices.at(i))); + else + format.merge(r.format); + } + } + } + return format; +} + +void QTextEngine::addRequiredBoundaries() const +{ + if (specialData) { + for (int i = 0; i < specialData->addFormats.size(); ++i) { + const QTextLayout::FormatRange &r = specialData->addFormats.at(i); + setBoundary(r.start); + setBoundary(r.start + r.length); + //qDebug("adding boundaries %d %d", r.start, r.start+r.length); + } + } +} + +bool QTextEngine::atWordSeparator(int position) const +{ + const QChar c = layoutData->string.at(position); + switch (c.toLatin1()) { + case '.': + case ',': + case '?': + case '!': + case '@': + case '#': + case '$': + case ':': + case ';': + case '-': + case '<': + case '>': + case '[': + case ']': + case '(': + case ')': + case '{': + case '}': + case '=': + case '/': + case '+': + case '%': + case '&': + case '^': + case '*': + case '\'': + case '"': + case '`': + case '~': + case '|': + return true; + default: + return false; + } +} + +bool QTextEngine::atSpace(int position) const +{ + const QChar c = layoutData->string.at(position); + + return c == QLatin1Char(' ') + || c == QChar::Nbsp + || c == QChar::LineSeparator + || c == QLatin1Char('\t') + ; +} + + +void QTextEngine::indexAdditionalFormats() +{ + if (!block.docHandle()) + return; + + specialData->addFormatIndices.resize(specialData->addFormats.count()); + QTextFormatCollection * const formats = this->formats(); + + for (int i = 0; i < specialData->addFormats.count(); ++i) { + specialData->addFormatIndices[i] = formats->indexForFormat(specialData->addFormats.at(i).format); + specialData->addFormats[i].format = QTextCharFormat(); + } +} + +/* These two helper functions are used to determine whether we need to insert a ZWJ character + between the text that gets truncated and the ellipsis. This is important to get + correctly shaped results for arabic text. +*/ +static bool nextCharJoins(const QString &string, int pos) +{ + while (pos < string.length() && string.at(pos).category() == QChar::Mark_NonSpacing) + ++pos; + if (pos == string.length()) + return false; + return string.at(pos).joining() != QChar::OtherJoining; +} + +static bool prevCharJoins(const QString &string, int pos) +{ + while (pos > 0 && string.at(pos - 1).category() == QChar::Mark_NonSpacing) + --pos; + if (pos == 0) + return false; + return (string.at(pos - 1).joining() == QChar::Dual || string.at(pos - 1).joining() == QChar::Center); +} + +QString QTextEngine::elidedText(Qt::TextElideMode mode, const QFixed &width, int flags) const +{ +// qDebug() << "elidedText; available width" << width.toReal() << "text width:" << this->width(0, layoutData->string.length()).toReal(); + + if (flags & Qt::TextShowMnemonic) { + itemize(); + HB_CharAttributes *attributes = const_cast(this->attributes()); + if (!attributes) + return QString(); + for (int i = 0; i < layoutData->items.size(); ++i) { + QScriptItem &si = layoutData->items[i]; + if (!si.num_glyphs) + shape(i); + + unsigned short *logClusters = this->logClusters(&si); + QGlyphLayout glyphs = shapedGlyphs(&si); + + const int end = si.position + length(&si); + for (int i = si.position; i < end - 1; ++i) { + if (layoutData->string.at(i) == QLatin1Char('&')) { + const int gp = logClusters[i - si.position]; + glyphs.attributes[gp].dontPrint = true; + attributes[i + 1].charStop = false; + attributes[i + 1].whiteSpace = false; + attributes[i + 1].lineBreakType = HB_NoBreak; + if (layoutData->string.at(i + 1) == QLatin1Char('&')) + ++i; + } + } + } + } + + validate(); + + if (mode == Qt::ElideNone + || this->width(0, layoutData->string.length()) <= width + || layoutData->string.length() <= 1) + return layoutData->string; + + QFixed ellipsisWidth; + QString ellipsisText; + { + QChar ellipsisChar(0x2026); + + QFontEngine *fe = fnt.d->engineForScript(QUnicodeTables::Common); + + QGlyphLayoutArray<1> ellipsisGlyph; + { + QFontEngine *feForEllipsis = (fe->type() == QFontEngine::Multi) + ? static_cast(fe)->engine(0) + : fe; + + if (feForEllipsis->type() == QFontEngine::Mac) + feForEllipsis = fe; + + // the lookup can be really slow when we use XLFD fonts + if (feForEllipsis->type() != QFontEngine::XLFD + && feForEllipsis->canRender(&ellipsisChar, 1)) { + int nGlyphs = 1; + feForEllipsis->stringToCMap(&ellipsisChar, 1, &ellipsisGlyph, &nGlyphs, 0); + } + } + + if (ellipsisGlyph.glyphs[0]) { + ellipsisWidth = ellipsisGlyph.advances_x[0]; + ellipsisText = ellipsisChar; + } else { + QString dotDotDot(QLatin1String("...")); + + QGlyphLayoutArray<3> glyphs; + int nGlyphs = 3; + if (!fe->stringToCMap(dotDotDot.constData(), 3, &glyphs, &nGlyphs, 0)) + // should never happen... + return layoutData->string; + for (int i = 0; i < nGlyphs; ++i) + ellipsisWidth += glyphs.advances_x[i]; + ellipsisText = dotDotDot; + } + } + + const QFixed availableWidth = width - ellipsisWidth; + if (availableWidth < 0) + return QString(); + + const HB_CharAttributes *attributes = this->attributes(); + if (!attributes) + return QString(); + + if (mode == Qt::ElideRight) { + QFixed currentWidth; + int pos; + int nextBreak = 0; + + do { + pos = nextBreak; + + ++nextBreak; + while (nextBreak < layoutData->string.length() && !attributes[nextBreak].charStop) + ++nextBreak; + + currentWidth += this->width(pos, nextBreak - pos); + } while (nextBreak < layoutData->string.length() + && currentWidth < availableWidth); + + if (nextCharJoins(layoutData->string, pos)) + ellipsisText.prepend(QChar(0x200d) /* ZWJ */); + + return layoutData->string.left(pos) + ellipsisText; + } else if (mode == Qt::ElideLeft) { + QFixed currentWidth; + int pos; + int nextBreak = layoutData->string.length(); + + do { + pos = nextBreak; + + --nextBreak; + while (nextBreak > 0 && !attributes[nextBreak].charStop) + --nextBreak; + + currentWidth += this->width(nextBreak, pos - nextBreak); + } while (nextBreak > 0 + && currentWidth < availableWidth); + + if (prevCharJoins(layoutData->string, pos)) + ellipsisText.append(QChar(0x200d) /* ZWJ */); + + return ellipsisText + layoutData->string.mid(pos); + } else if (mode == Qt::ElideMiddle) { + QFixed leftWidth; + QFixed rightWidth; + + int leftPos = 0; + int nextLeftBreak = 0; + + int rightPos = layoutData->string.length(); + int nextRightBreak = layoutData->string.length(); + + do { + leftPos = nextLeftBreak; + rightPos = nextRightBreak; + + ++nextLeftBreak; + while (nextLeftBreak < layoutData->string.length() && !attributes[nextLeftBreak].charStop) + ++nextLeftBreak; + + --nextRightBreak; + while (nextRightBreak > 0 && !attributes[nextRightBreak].charStop) + --nextRightBreak; + + leftWidth += this->width(leftPos, nextLeftBreak - leftPos); + rightWidth += this->width(nextRightBreak, rightPos - nextRightBreak); + } while (nextLeftBreak < layoutData->string.length() + && nextRightBreak > 0 + && leftWidth + rightWidth < availableWidth); + + if (nextCharJoins(layoutData->string, leftPos)) + ellipsisText.prepend(QChar(0x200d) /* ZWJ */); + if (prevCharJoins(layoutData->string, rightPos)) + ellipsisText.append(QChar(0x200d) /* ZWJ */); + + return layoutData->string.left(leftPos) + ellipsisText + layoutData->string.mid(rightPos); + } + + return layoutData->string; +} + +void QTextEngine::setBoundary(int strPos) const +{ + if (strPos <= 0 || strPos >= layoutData->string.length()) + return; + + int itemToSplit = 0; + while (itemToSplit < layoutData->items.size() && layoutData->items.at(itemToSplit).position <= strPos) + itemToSplit++; + itemToSplit--; + if (layoutData->items.at(itemToSplit).position == strPos) { + // already a split at the requested position + return; + } + splitItem(itemToSplit, strPos - layoutData->items.at(itemToSplit).position); +} + +void QTextEngine::splitItem(int item, int pos) const +{ + if (pos <= 0) + return; + + layoutData->items.insert(item + 1, layoutData->items[item]); + QScriptItem &oldItem = layoutData->items[item]; + QScriptItem &newItem = layoutData->items[item+1]; + newItem.position += pos; + + if (oldItem.num_glyphs) { + // already shaped, break glyphs aswell + int breakGlyph = logClusters(&oldItem)[pos]; + + newItem.num_glyphs = oldItem.num_glyphs - breakGlyph; + oldItem.num_glyphs = breakGlyph; + newItem.glyph_data_offset = oldItem.glyph_data_offset + breakGlyph; + + for (int i = 0; i < newItem.num_glyphs; i++) + logClusters(&newItem)[i] -= breakGlyph; + + QFixed w = 0; + const QGlyphLayout g = shapedGlyphs(&oldItem); + for(int j = 0; j < breakGlyph; ++j) + w += g.advances_x[j]; + + newItem.width = oldItem.width - w; + oldItem.width = w; + } + +// qDebug("split at position %d itempos=%d", pos, item); +} + +QFixed QTextEngine::calculateTabWidth(int item, QFixed x) const +{ + const QScriptItem &si = layoutData->items[item]; + + QFixed dpiScale = 1; + if (block.docHandle() && block.docHandle()->layout()) { + QPaintDevice *pdev = block.docHandle()->layout()->paintDevice(); + if (pdev) + dpiScale = QFixed::fromReal(pdev->logicalDpiY() / qreal(qt_defaultDpiY())); + } else { + dpiScale = QFixed::fromReal(fnt.d->dpi / qreal(qt_defaultDpiY())); + } + + QList tabArray = option.tabs(); + if (!tabArray.isEmpty()) { + if (isRightToLeft()) { // rebase the tabArray positions. + QList newTabs; + QList::Iterator iter = tabArray.begin(); + while(iter != tabArray.end()) { + QTextOption::Tab tab = *iter; + if (tab.type == QTextOption::LeftTab) + tab.type = QTextOption::RightTab; + else if (tab.type == QTextOption::RightTab) + tab.type = QTextOption::LeftTab; + newTabs << tab; + ++iter; + } + tabArray = newTabs; + } + for (int i = 0; i < tabArray.size(); ++i) { + QFixed tab = QFixed::fromReal(tabArray[i].position) * dpiScale; + if (tab > x) { // this is the tab we need. + QTextOption::Tab tabSpec = tabArray[i]; + int tabSectionEnd = layoutData->string.count(); + if (tabSpec.type == QTextOption::RightTab || tabSpec.type == QTextOption::CenterTab) { + // find next tab to calculate the width required. + tab = QFixed::fromReal(tabSpec.position); + for (int i=item + 1; i < layoutData->items.count(); i++) { + const QScriptItem &item = layoutData->items[i]; + if (item.analysis.flags == QScriptAnalysis::TabOrObject) { // found it. + tabSectionEnd = item.position; + break; + } + } + } + else if (tabSpec.type == QTextOption::DelimiterTab) + // find delimitor character to calculate the width required + tabSectionEnd = qMax(si.position, layoutData->string.indexOf(tabSpec.delimiter, si.position) + 1); + + if (tabSectionEnd > si.position) { + QFixed length; + // Calculate the length of text between this tab and the tabSectionEnd + for (int i=item; i < layoutData->items.count(); i++) { + QScriptItem &item = layoutData->items[i]; + if (item.position > tabSectionEnd || item.position <= si.position) + continue; + shape(i); // first, lets make sure relevant text is already shaped + QGlyphLayout glyphs = this->shapedGlyphs(&item); + const int end = qMin(item.position + item.num_glyphs, tabSectionEnd) - item.position; + for (int i=0; i < end; i++) + length += glyphs.advances_x[i] * !glyphs.attributes[i].dontPrint; + if (end + item.position == tabSectionEnd && tabSpec.type == QTextOption::DelimiterTab) // remove half of matching char + length -= glyphs.advances_x[end] / 2 * !glyphs.attributes[end].dontPrint; + } + + switch (tabSpec.type) { + case QTextOption::CenterTab: + length /= 2; + // fall through + case QTextOption::DelimiterTab: + // fall through + case QTextOption::RightTab: + tab = QFixed::fromReal(tabSpec.position) * dpiScale - length; + if (tab < 0) // default to tab taking no space + return QFixed(); + break; + case QTextOption::LeftTab: + break; + } + } + return tab - x; + } + } + } + QFixed tab = QFixed::fromReal(option.tabStop()); + if (tab <= 0) + tab = 80; // default + tab *= dpiScale; + QFixed nextTabPos = ((x / tab).truncate() + 1) * tab; + QFixed tabWidth = nextTabPos - x; + + return tabWidth; +} + +void QTextEngine::resolveAdditionalFormats() const +{ + if (!specialData || specialData->addFormats.isEmpty() + || !block.docHandle() + || !specialData->resolvedFormatIndices.isEmpty()) + return; + + QTextFormatCollection *collection = this->formats(); + + specialData->resolvedFormatIndices.clear(); + QVector indices(layoutData->items.count()); + for (int i = 0; i < layoutData->items.count(); ++i) { + QTextCharFormat f = format(&layoutData->items.at(i)); + indices[i] = collection->indexForFormat(f); + } + specialData->resolvedFormatIndices = indices; +} + +QFixed QTextEngine::leadingSpaceWidth(const QScriptLine &line) +{ + if (!line.hasTrailingSpaces + || (option.flags() & QTextOption::IncludeTrailingSpaces) + || !isRightToLeft()) + return QFixed(); + + int pos = line.length; + const HB_CharAttributes *attributes = this->attributes(); + if (!attributes) + return QFixed(); + while (pos > 0 && attributes[line.from + pos - 1].whiteSpace) + --pos; + return width(line.from + pos, line.length - pos); +} + +QStackTextEngine::QStackTextEngine(const QString &string, const QFont &f) + : QTextEngine(string, f), + _layoutData(string, _memory, MemSize) +{ + stackEngine = true; + layoutData = &_layoutData; +} + +QTextItemInt::QTextItemInt(const QScriptItem &si, QFont *font, const QTextCharFormat &format) + : justified(false), underlineStyle(QTextCharFormat::NoUnderline), charFormat(format), + num_chars(0), chars(0), logClusters(0), f(0), fontEngine(0) +{ + // explicitly initialize flags so that initFontAttributes can be called + // multiple times on the same TextItem + flags = 0; + if (si.analysis.bidiLevel %2) + flags |= QTextItem::RightToLeft; + ascent = si.ascent; + descent = si.descent; + f = font; + fontEngine = f->d->engineForScript(si.analysis.script); + Q_ASSERT(fontEngine); + + if (format.hasProperty(QTextFormat::TextUnderlineStyle)) { + underlineStyle = format.underlineStyle(); + } else if (format.boolProperty(QTextFormat::FontUnderline) + || f->d->underline) { + underlineStyle = QTextCharFormat::SingleUnderline; + } + + // compat + if (underlineStyle == QTextCharFormat::SingleUnderline) + flags |= QTextItem::Underline; + + if (f->d->overline || format.fontOverline()) + flags |= QTextItem::Overline; + if (f->d->strikeOut || format.fontStrikeOut()) + flags |= QTextItem::StrikeOut; +} + +QTextItemInt::QTextItemInt(const QGlyphLayout &g, QFont *font, const QChar *chars_, int numChars, QFontEngine *fe) + : flags(0), justified(false), underlineStyle(QTextCharFormat::NoUnderline), + num_chars(numChars), chars(chars_), logClusters(0), f(font), glyphs(g), fontEngine(fe) +{ +} + +QTextItemInt QTextItemInt::midItem(QFontEngine *fontEngine, int firstGlyphIndex, int numGlyphs) const +{ + QTextItemInt ti = *this; + const int end = firstGlyphIndex + numGlyphs; + ti.glyphs = glyphs.mid(firstGlyphIndex, numGlyphs); + ti.fontEngine = fontEngine; + + if (logClusters && chars) { + const int logClusterOffset = logClusters[0]; + while (logClusters[ti.chars - chars] - logClusterOffset < firstGlyphIndex) + ++ti.chars; + + ti.logClusters += (ti.chars - chars); + + ti.num_chars = 0; + int char_start = ti.chars - chars; + while (char_start + ti.num_chars < num_chars && ti.logClusters[ti.num_chars] - logClusterOffset < end) + ++ti.num_chars; + } + return ti; +} + + +QTransform qt_true_matrix(qreal w, qreal h, QTransform x) +{ + QRectF rect = x.mapRect(QRectF(0, 0, w, h)); + return x * QTransform::fromTranslate(-rect.x(), -rect.y()); +} + + +glyph_metrics_t glyph_metrics_t::transformed(const QTransform &matrix) const +{ + if (matrix.type() < QTransform::TxTranslate) + return *this; + + glyph_metrics_t m = *this; + + qreal w = width.toReal(); + qreal h = height.toReal(); + QTransform xform = qt_true_matrix(w, h, matrix); + + QRectF rect(0, 0, w, h); + rect = xform.mapRect(rect); + m.width = QFixed::fromReal(rect.width()); + m.height = QFixed::fromReal(rect.height()); + + QLineF l = xform.map(QLineF(x.toReal(), y.toReal(), xoff.toReal(), yoff.toReal())); + + m.x = QFixed::fromReal(l.x1()); + m.y = QFixed::fromReal(l.y1()); + + // The offset is relative to the baseline which is why we use dx/dy of the line + m.xoff = QFixed::fromReal(l.dx()); + m.yoff = QFixed::fromReal(l.dy()); + + return m; +} + + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextengine_mac.cpp b/src/gui/text/qtextengine_mac.cpp new file mode 100644 index 0000000000..97e8c5b0f8 --- /dev/null +++ b/src/gui/text/qtextengine_mac.cpp @@ -0,0 +1,656 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextengine_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +// set the glyph attributes heuristically. Assumes a 1 to 1 relationship between chars and glyphs +// and no reordering. +// also computes logClusters heuristically +static void heuristicSetGlyphAttributes(const QChar *uc, int length, QGlyphLayout *glyphs, unsigned short *logClusters, int num_glyphs) +{ + // ### zeroWidth and justification are missing here!!!!! + + Q_UNUSED(num_glyphs); + +// qDebug("QScriptEngine::heuristicSetGlyphAttributes, num_glyphs=%d", item->num_glyphs); + + const bool symbolFont = false; // #### + glyphs->attributes[0].mark = false; + glyphs->attributes[0].clusterStart = true; + glyphs->attributes[0].dontPrint = (!symbolFont && uc[0].unicode() == 0x00ad) || qIsControlChar(uc[0].unicode()); + + int pos = 0; + int lastCat = QChar::category(uc[0].unicode()); + for (int i = 1; i < length; ++i) { + if (logClusters[i] == pos) + // same glyph + continue; + ++pos; + while (pos < logClusters[i]) { + ++pos; + } + // hide soft-hyphens by default + if ((!symbolFont && uc[i].unicode() == 0x00ad) || qIsControlChar(uc[i].unicode())) + glyphs->attributes[pos].dontPrint = true; + const QUnicodeTables::Properties *prop = QUnicodeTables::properties(uc[i].unicode()); + int cat = prop->category; + + // one gets an inter character justification point if the current char is not a non spacing mark. + // as then the current char belongs to the last one and one gets a space justification point + // after the space char. + if (lastCat == QChar::Separator_Space) + glyphs->attributes[pos-1].justification = HB_Space; + else if (cat != QChar::Mark_NonSpacing) + glyphs->attributes[pos-1].justification = HB_Character; + else + glyphs->attributes[pos-1].justification = HB_NoJustification; + + lastCat = cat; + } + pos = logClusters[length-1]; + if (lastCat == QChar::Separator_Space) + glyphs->attributes[pos].justification = HB_Space; + else + glyphs->attributes[pos].justification = HB_Character; +} + +struct QArabicProperties { + unsigned char shape; + unsigned char justification; +}; +Q_DECLARE_TYPEINFO(QArabicProperties, Q_PRIMITIVE_TYPE); + +enum QArabicShape { + XIsolated, + XFinal, + XInitial, + XMedial, + // intermediate state + XCausing +}; + + +// these groups correspond to the groups defined in the Unicode standard. +// Some of these groups are equal with regards to both joining and line breaking behaviour, +// and thus have the same enum value +// +// I'm not sure the mapping of syriac to arabic enums is correct with regards to justification, but as +// I couldn't find any better document I'll hope for the best. +enum ArabicGroup { + // NonJoining + ArabicNone, + ArabicSpace, + // Transparent + Transparent, + // Causing + Center, + Kashida, + + // Arabic + // Dual + Beh, + Noon, + Meem = Noon, + Heh = Noon, + KnottedHeh = Noon, + HehGoal = Noon, + SwashKaf = Noon, + Yeh, + Hah, + Seen, + Sad = Seen, + Tah, + Kaf = Tah, + Gaf = Tah, + Lam = Tah, + Ain, + Feh = Ain, + Qaf = Ain, + // Right + Alef, + Waw, + Dal, + TehMarbuta = Dal, + Reh, + HamzaOnHehGoal, + YehWithTail = HamzaOnHehGoal, + YehBarre = HamzaOnHehGoal, + + // Syriac + // Dual + Beth = Beh, + Gamal = Ain, + Heth = Noon, + Teth = Hah, + Yudh = Noon, + Kaph = Noon, + Lamadh = Lam, + Mim = Noon, + Nun = Noon, + Semakh = Noon, + FinalSemakh = Noon, + SyriacE = Ain, + Pe = Ain, + ReversedPe = Hah, + Qaph = Noon, + Shin = Noon, + Fe = Ain, + + // Right + Alaph = Alef, + Dalath = Dal, + He = Dal, + SyriacWaw = Waw, + Zain = Alef, + YudhHe = Waw, + Sadhe = HamzaOnHehGoal, + Taw = Dal, + + // Compiler bug? Otherwise ArabicGroupsEnd would be equal to Dal + 1. + Dummy = HamzaOnHehGoal, + ArabicGroupsEnd +}; + +static const unsigned char arabic_group[0x150] = { + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + + ArabicNone, ArabicNone, Alef, Alef, + Waw, Alef, Yeh, Alef, + Beh, TehMarbuta, Beh, Beh, + Hah, Hah, Hah, Dal, + + Dal, Reh, Reh, Seen, + Seen, Sad, Sad, Tah, + Tah, Ain, Ain, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + + // 0x640 + Kashida, Feh, Qaf, Kaf, + Lam, Meem, Noon, Heh, + Waw, Yeh, Yeh, Transparent, + Transparent, Transparent, Transparent, Transparent, + + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, Transparent, Transparent, + Transparent, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, Beh, Qaf, + + Transparent, Alef, Alef, Alef, + ArabicNone, Alef, Waw, Waw, + Yeh, Beh, Beh, Beh, + Beh, Beh, Beh, Beh, + + // 0x680 + Beh, Hah, Hah, Hah, + Hah, Hah, Hah, Hah, + Dal, Dal, Dal, Dal, + Dal, Dal, Dal, Dal, + + Dal, Reh, Reh, Reh, + Reh, Reh, Reh, Reh, + Reh, Reh, Seen, Seen, + Seen, Sad, Sad, Tah, + + Ain, Feh, Feh, Feh, + Feh, Feh, Feh, Qaf, + Qaf, Gaf, SwashKaf, Gaf, + Kaf, Kaf, Kaf, Gaf, + + Gaf, Gaf, Gaf, Gaf, + Gaf, Lam, Lam, Lam, + Lam, Noon, Noon, Noon, + Noon, Noon, KnottedHeh, Hah, + + // 0x6c0 + TehMarbuta, HehGoal, HamzaOnHehGoal, HamzaOnHehGoal, + Waw, Waw, Waw, Waw, + Waw, Waw, Waw, Waw, + Yeh, YehWithTail, Yeh, Waw, + + Yeh, Yeh, YehBarre, YehBarre, + ArabicNone, TehMarbuta, Transparent, Transparent, + Transparent, Transparent, Transparent, Transparent, + Transparent, ArabicNone, ArabicNone, Transparent, + + Transparent, Transparent, Transparent, Transparent, + Transparent, ArabicNone, ArabicNone, Transparent, + Transparent, ArabicNone, Transparent, Transparent, + Transparent, Transparent, Dal, Reh, + + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, Seen, Sad, + Ain, ArabicNone, ArabicNone, KnottedHeh, + + // 0x700 + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + ArabicNone, ArabicNone, ArabicNone, ArabicNone, + + Alaph, Transparent, Beth, Gamal, + Gamal, Dalath, Dalath, He, + SyriacWaw, Zain, Heth, Teth, + Teth, Yudh, YudhHe, Kaph, + + Lamadh, Mim, Nun, Semakh, + FinalSemakh, SyriacE, Pe, ReversedPe, + Sadhe, Qaph, Dalath, Shin, + Taw, Beth, Gamal, Dalath, + + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, Transparent, Transparent, + + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, Transparent, Transparent, + Transparent, Transparent, Transparent, ArabicNone, + ArabicNone, Zain, Kaph, Fe, +}; + +static inline ArabicGroup arabicGroup(unsigned short uc) +{ + if (uc >= 0x0600 && uc < 0x750) + return (ArabicGroup) arabic_group[uc-0x600]; + else if (uc == 0x200d) + return Center; + else if (QChar::category(uc) == QChar::Separator_Space) + return ArabicSpace; + else + return ArabicNone; +} + + +/* + Arabic shaping obeys a number of rules according to the joining classes (see Unicode book, section on + arabic). + + Each unicode char has a joining class (right, dual (left&right), center (joincausing) or transparent). + transparent joining is not encoded in QChar::joining(), but applies to all combining marks and format marks. + + Right join-causing: dual + center + Left join-causing: dual + right + center + + Rules are as follows (for a string already in visual order, as we have it here): + + R1 Transparent characters do not affect joining behaviour. + R2 A right joining character, that has a right join-causing char on the right will get form XRight + (R3 A left joining character, that has a left join-causing char on the left will get form XLeft) + Note: the above rule is meaningless, as there are no pure left joining characters defined in Unicode + R4 A dual joining character, that has a left join-causing char on the left and a right join-causing char on + the right will get form XMedial + R5 A dual joining character, that has a right join causing char on the right, and no left join causing char on the left + will get form XRight + R6 A dual joining character, that has a left join causing char on the left, and no right join causing char on the right + will get form XLeft + R7 Otherwise the character will get form XIsolated + + Additionally we have to do the minimal ligature support for lam-alef ligatures: + + L1 Transparent characters do not affect ligature behaviour. + L2 Any sequence of Alef(XRight) + Lam(XMedial) will form the ligature Alef.Lam(XLeft) + L3 Any sequence of Alef(XRight) + Lam(XLeft) will form the ligature Alef.Lam(XIsolated) + + The state table below handles rules R1-R7. +*/ + +enum Joining { + JNone, + JCausing, + JDual, + JRight, + JTransparent +}; + +static const Joining joining_for_group[ArabicGroupsEnd] = { + // NonJoining + JNone, // ArabicNone + JNone, // ArabicSpace + // Transparent + JTransparent, // Transparent + // Causing + JCausing, // Center + JCausing, // Kashida + // Dual + JDual, // Beh + JDual, // Noon + JDual, // Yeh + JDual, // Hah + JDual, // Seen + JDual, // Tah + JDual, // Ain + // Right + JRight, // Alef + JRight, // Waw + JRight, // Dal + JRight, // Reh + JRight // HamzaOnHehGoal +}; + + +struct JoiningPair { + QArabicShape form1; + QArabicShape form2; +}; + +static const JoiningPair joining_table[5][4] = +// None, Causing, Dual, Right +{ + { { XIsolated, XIsolated }, { XIsolated, XCausing }, { XIsolated, XInitial }, { XIsolated, XIsolated } }, // XIsolated + { { XFinal, XIsolated }, { XFinal, XCausing }, { XFinal, XInitial }, { XFinal, XIsolated } }, // XFinal + { { XIsolated, XIsolated }, { XInitial, XCausing }, { XInitial, XMedial }, { XInitial, XFinal } }, // XInitial + { { XFinal, XIsolated }, { XMedial, XCausing }, { XMedial, XMedial }, { XMedial, XFinal } }, // XMedial + { { XIsolated, XIsolated }, { XIsolated, XCausing }, { XIsolated, XMedial }, { XIsolated, XFinal } }, // XCausing +}; + + +/* +According to http://www.microsoft.com/middleeast/Arabicdev/IE6/KBase.asp + +1. Find the priority of the connecting opportunities in each word +2. Add expansion at the highest priority connection opportunity +3. If more than one connection opportunity have the same highest value, + use the opportunity closest to the end of the word. + +Following is a chart that provides the priority for connection +opportunities and where expansion occurs. The character group names +are those in table 6.6 of the UNICODE 2.0 book. + + +PrioritY Glyph Condition Kashida Location + +Arabic_Kashida User inserted Kashida The user entered a Kashida in a position. After the user + (Shift+j or Shift+[E with hat]) Thus, it is the highest priority to insert an inserted kashida + automatic kashida. + +Arabic_Seen Seen, Sad Connecting to the next character. After the character. + (Initial or medial form). + +Arabic_HaaDal Teh Marbutah, Haa, Dal Connecting to previous character. Before the final form + of these characters. + +Arabic_Alef Alef, Tah, Lam, Connecting to previous character. Before the final form + Kaf and Gaf of these characters. + +Arabic_BaRa Reh, Yeh Connected to medial Beh Before preceding medial Baa + +Arabic_Waw Waw, Ain, Qaf, Feh Connecting to previous character. Before the final form of + these characters. + +Arabic_Normal Other connecting Connecting to previous character. Before the final form + characters of these characters. + + + +This seems to imply that we have at most one kashida point per arabic word. + +*/ + +void qt_getArabicProperties(const unsigned short *chars, int len, QArabicProperties *properties) +{ +// qDebug("arabicSyriacOpenTypeShape: properties:"); + int lastPos = 0; + int lastGroup = ArabicNone; + + ArabicGroup group = arabicGroup(chars[0]); + Joining j = joining_for_group[group]; + QArabicShape shape = joining_table[XIsolated][j].form2; + properties[0].justification = HB_NoJustification; + + for (int i = 1; i < len; ++i) { + // #### fix handling for spaces and punktuation + properties[i].justification = HB_NoJustification; + + group = arabicGroup(chars[i]); + j = joining_for_group[group]; + + if (j == JTransparent) { + properties[i].shape = XIsolated; + continue; + } + + properties[lastPos].shape = joining_table[shape][j].form1; + shape = joining_table[shape][j].form2; + + switch(lastGroup) { + case Seen: + if (properties[lastPos].shape == XInitial || properties[lastPos].shape == XMedial) + properties[i-1].justification = HB_Arabic_Seen; + break; + case Hah: + if (properties[lastPos].shape == XFinal) + properties[lastPos-1].justification = HB_Arabic_HaaDal; + break; + case Alef: + if (properties[lastPos].shape == XFinal) + properties[lastPos-1].justification = HB_Arabic_Alef; + break; + case Ain: + if (properties[lastPos].shape == XFinal) + properties[lastPos-1].justification = HB_Arabic_Waw; + break; + case Noon: + if (properties[lastPos].shape == XFinal) + properties[lastPos-1].justification = HB_Arabic_Normal; + break; + case ArabicNone: + break; + + default: + Q_ASSERT(false); + } + + lastGroup = ArabicNone; + + switch(group) { + case ArabicNone: + case Transparent: + // ### Center should probably be treated as transparent when it comes to justification. + case Center: + break; + case ArabicSpace: + properties[i].justification = HB_Arabic_Space; + break; + case Kashida: + properties[i].justification = HB_Arabic_Kashida; + break; + case Seen: + lastGroup = Seen; + break; + + case Hah: + case Dal: + lastGroup = Hah; + break; + + case Alef: + case Tah: + lastGroup = Alef; + break; + + case Yeh: + case Reh: + if (properties[lastPos].shape == XMedial && arabicGroup(chars[lastPos]) == Beh) + properties[lastPos-1].justification = HB_Arabic_BaRa; + break; + + case Ain: + case Waw: + lastGroup = Ain; + break; + + case Noon: + case Beh: + case HamzaOnHehGoal: + lastGroup = Noon; + break; + case ArabicGroupsEnd: + Q_ASSERT(false); + } + + lastPos = i; + } + properties[lastPos].shape = joining_table[shape][JNone].form1; + + +// for (int i = 0; i < len; ++i) +// qDebug("arabic properties(%d): uc=%x shape=%d, justification=%d", i, chars[i], properties[i].shape, properties[i].justification); +} + +void QTextEngine::shapeTextMac(int item) const +{ + QScriptItem &si = layoutData->items[item]; + + si.glyph_data_offset = layoutData->used; + + QFontEngine *font = fontEngine(si, &si.ascent, &si.descent, &si.leading); + if (font->type() != QFontEngine::Multi) { + shapeTextWithHarfbuzz(item); + return; + } + +#ifndef QT_MAC_USE_COCOA + QFontEngineMacMulti *fe = static_cast(font); +#else + QCoreTextFontEngineMulti *fe = static_cast(font); +#endif + QTextEngine::ShaperFlags flags; + if (si.analysis.bidiLevel % 2) + flags |= RightToLeft; + if (option.useDesignMetrics()) + flags |= DesignMetrics; + + attributes(); // pre-initialize char attributes + + const int len = length(item); + int num_glyphs = length(item); + const QChar *str = layoutData->string.unicode() + si.position; + ushort upperCased[256]; + if (si.analysis.flags == QScriptAnalysis::SmallCaps || si.analysis.flags == QScriptAnalysis::Uppercase + || si.analysis.flags == QScriptAnalysis::Lowercase) { + ushort *uc = upperCased; + if (len > 256) + uc = new ushort[len]; + for (int i = 0; i < len; ++i) { + if(si.analysis.flags == QScriptAnalysis::Lowercase) + uc[i] = str[i].toLower().unicode(); + else + uc[i] = str[i].toUpper().unicode(); + } + str = reinterpret_cast(uc); + } + + ensureSpace(num_glyphs); + num_glyphs = layoutData->glyphLayout.numGlyphs - layoutData->used; + + QGlyphLayout g = availableGlyphs(&si); + g.numGlyphs = num_glyphs; + unsigned short *log_clusters = logClusters(&si); + + bool stringToCMapFailed = false; + if (!fe->stringToCMap(str, len, &g, &num_glyphs, flags, log_clusters, attributes())) { + ensureSpace(num_glyphs); + g = availableGlyphs(&si); + stringToCMapFailed = !fe->stringToCMap(str, len, &g, &num_glyphs, flags, log_clusters, + attributes()); + } + + if (!stringToCMapFailed) { + heuristicSetGlyphAttributes(str, len, &g, log_clusters, num_glyphs); + + si.num_glyphs = num_glyphs; + + layoutData->used += si.num_glyphs; + + QGlyphLayout g = shapedGlyphs(&si); + + if (si.analysis.script == QUnicodeTables::Arabic) { + QVarLengthArray props(len + 2); + QArabicProperties *properties = props.data(); + int f = si.position; + int l = len; + if (f > 0) { + --f; + ++l; + ++properties; + } + if (f + l < layoutData->string.length()) { + ++l; + } + qt_getArabicProperties((const unsigned short *)(layoutData->string.unicode()+f), l, props.data()); + + unsigned short *log_clusters = logClusters(&si); + + for (int i = 0; i < len; ++i) { + int gpos = log_clusters[i]; + g.attributes[gpos].justification = properties[i].justification; + } + } + } + + const ushort *uc = reinterpret_cast(str); + + if ((si.analysis.flags == QScriptAnalysis::SmallCaps || si.analysis.flags == QScriptAnalysis::Uppercase + || si.analysis.flags == QScriptAnalysis::Lowercase) + && uc != upperCased) + delete [] uc; +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextengine_p.h b/src/gui/text/qtextengine_p.h new file mode 100644 index 0000000000..366c5c3207 --- /dev/null +++ b/src/gui/text/qtextengine_p.h @@ -0,0 +1,643 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTENGINE_P_H +#define QTEXTENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qglobal.h" +#include "QtCore/qstring.h" +#include "QtCore/qvarlengtharray.h" +#include "QtCore/qnamespace.h" +#include "QtGui/qtextlayout.h" +#include "private/qtextformat_p.h" +#include "private/qfont_p.h" +#include "QtCore/qvector.h" +#include "QtGui/qpaintengine.h" +#include "QtGui/qtextobject.h" +#include "QtGui/qtextoption.h" +#include "QtCore/qset.h" +#include "QtCore/qdebug.h" +#ifndef QT_BUILD_COMPAT_LIB +#include "private/qtextdocument_p.h" +#endif +#include "private/qharfbuzz_p.h" +#include "private/qfixed_p.h" + +#include + +QT_BEGIN_NAMESPACE + +class QFontPrivate; +class QFontEngine; + +class QString; +class QPainter; + +class QAbstractTextDocumentLayout; + + +// this uses the same coordinate system as Qt, but a different one to freetype. +// * y is usually negative, and is equal to the ascent. +// * negative yoff means the following stuff is drawn higher up. +// the characters bounding rect is given by QRect(x,y,width,height), its advance by +// xoo and yoff +struct glyph_metrics_t +{ + inline glyph_metrics_t() + : x(100000), y(100000) {} + inline glyph_metrics_t(QFixed _x, QFixed _y, QFixed _width, QFixed _height, QFixed _xoff, QFixed _yoff) + : x(_x), + y(_y), + width(_width), + height(_height), + xoff(_xoff), + yoff(_yoff) + {} + QFixed x; + QFixed y; + QFixed width; + QFixed height; + QFixed xoff; + QFixed yoff; + + glyph_metrics_t transformed(const QTransform &xform) const; + inline bool isValid() const {return x != 100000 && y != 100000;} +}; +Q_DECLARE_TYPEINFO(glyph_metrics_t, Q_PRIMITIVE_TYPE); + +struct Q_AUTOTEST_EXPORT QScriptAnalysis +{ + enum Flags { + None = 0, + Lowercase = 1, + Uppercase = 2, + SmallCaps = 3, + LineOrParagraphSeparator = 4, + Space = 5, + SpaceTabOrObject = Space, + Tab = 6, + TabOrObject = Tab, + Object = 7 + }; + unsigned short script : 7; + unsigned short bidiLevel : 6; // Unicode Bidi algorithm embedding level (0-61) + unsigned short flags : 3; + inline bool operator == (const QScriptAnalysis &other) const { + return script == other.script && bidiLevel == other.bidiLevel && flags == other.flags; + } +}; +Q_DECLARE_TYPEINFO(QScriptAnalysis, Q_PRIMITIVE_TYPE); + +struct QGlyphJustification +{ + inline QGlyphJustification() + : type(0), nKashidas(0), space_18d6(0) + {} + + enum JustificationType { + JustifyNone, + JustifySpace, + JustifyKashida + }; + + uint type :2; + uint nKashidas : 6; // more do not make sense... + uint space_18d6 : 24; +}; +Q_DECLARE_TYPEINFO(QGlyphJustification, Q_PRIMITIVE_TYPE); + +struct QGlyphLayoutInstance +{ + QFixedPoint offset; + QFixedPoint advance; + HB_Glyph glyph; + QGlyphJustification justification; + HB_GlyphAttributes attributes; +}; + +struct QGlyphLayout +{ + // init to 0 not needed, done when shaping + QFixedPoint *offsets; // 8 bytes per element + HB_Glyph *glyphs; // 4 bytes per element + QFixed *advances_x; // 4 bytes per element + QFixed *advances_y; // 4 bytes per element + QGlyphJustification *justifications; // 4 bytes per element + HB_GlyphAttributes *attributes; // 2 bytes per element + + int numGlyphs; + + inline QGlyphLayout() : numGlyphs(0) {} + + inline explicit QGlyphLayout(char *address, int totalGlyphs) + { + offsets = reinterpret_cast(address); + int offset = totalGlyphs * sizeof(HB_FixedPoint); + glyphs = reinterpret_cast(address + offset); + offset += totalGlyphs * sizeof(HB_Glyph); + advances_x = reinterpret_cast(address + offset); + offset += totalGlyphs * sizeof(QFixed); + advances_y = reinterpret_cast(address + offset); + offset += totalGlyphs * sizeof(QFixed); + justifications = reinterpret_cast(address + offset); + offset += totalGlyphs * sizeof(QGlyphJustification); + attributes = reinterpret_cast(address + offset); + numGlyphs = totalGlyphs; + } + + inline QGlyphLayout mid(int position, int n = -1) const { + QGlyphLayout copy = *this; + copy.glyphs += position; + copy.advances_x += position; + copy.advances_y += position; + copy.offsets += position; + copy.justifications += position; + copy.attributes += position; + if (n == -1) + copy.numGlyphs -= position; + else + copy.numGlyphs = n; + return copy; + } + + static inline int spaceNeededForGlyphLayout(int totalGlyphs) { + return totalGlyphs * (sizeof(HB_Glyph) + sizeof(HB_GlyphAttributes) + + sizeof(QFixed) + sizeof(QFixed) + sizeof(QFixedPoint) + + sizeof(QGlyphJustification)); + } + + inline QFixed effectiveAdvance(int item) const + { return (advances_x[item] + QFixed::fromFixed(justifications[item].space_18d6)) * !attributes[item].dontPrint; } + + inline QGlyphLayoutInstance instance(int position) const { + QGlyphLayoutInstance g; + g.offset.x = offsets[position].x; + g.offset.y = offsets[position].y; + g.glyph = glyphs[position]; + g.advance.x = advances_x[position]; + g.advance.y = advances_y[position]; + g.justification = justifications[position]; + g.attributes = attributes[position]; + return g; + } + + inline void setInstance(int position, const QGlyphLayoutInstance &g) { + offsets[position].x = g.offset.x; + offsets[position].y = g.offset.y; + glyphs[position] = g.glyph; + advances_x[position] = g.advance.x; + advances_y[position] = g.advance.y; + justifications[position] = g.justification; + attributes[position] = g.attributes; + } + + inline void clear(int first = 0, int last = -1) { + if (last == -1) + last = numGlyphs; + if (first == 0 && last == numGlyphs + && reinterpret_cast(offsets + numGlyphs) == reinterpret_cast(glyphs)) { + memset(offsets, 0, spaceNeededForGlyphLayout(numGlyphs)); + } else { + const int num = last - first; + memset(offsets + first, 0, num * sizeof(QFixedPoint)); + memset(glyphs + first, 0, num * sizeof(HB_Glyph)); + memset(advances_x + first, 0, num * sizeof(QFixed)); + memset(advances_y + first, 0, num * sizeof(QFixed)); + memset(justifications + first, 0, num * sizeof(QGlyphJustification)); + memset(attributes + first, 0, num * sizeof(HB_GlyphAttributes)); + } + } + + inline char *data() { + return reinterpret_cast(offsets); + } + + void grow(char *address, int totalGlyphs); +}; + +class QVarLengthGlyphLayoutArray : private QVarLengthArray, public QGlyphLayout +{ +private: + typedef QVarLengthArray Array; +public: + QVarLengthGlyphLayoutArray(int totalGlyphs) + : Array(spaceNeededForGlyphLayout(totalGlyphs) / sizeof(void *) + 1) + , QGlyphLayout(reinterpret_cast(Array::data()), totalGlyphs) + { + memset(Array::data(), 0, Array::size() * sizeof(void *)); + } + + void resize(int totalGlyphs) + { + Array::resize(spaceNeededForGlyphLayout(totalGlyphs) / sizeof(void *) + 1); + + *((QGlyphLayout *)this) = QGlyphLayout(reinterpret_cast(Array::data()), totalGlyphs); + memset(Array::data(), 0, Array::size() * sizeof(void *)); + } +}; + +template struct QGlyphLayoutArray : public QGlyphLayout +{ +public: + QGlyphLayoutArray() + : QGlyphLayout(reinterpret_cast(buffer), N) + { + memset(buffer, 0, sizeof(buffer)); + } + +private: + void *buffer[(N * (sizeof(HB_Glyph) + sizeof(HB_GlyphAttributes) + + sizeof(QFixed) + sizeof(QFixed) + sizeof(QFixedPoint) + + sizeof(QGlyphJustification))) + / sizeof(void *) + 1]; +}; + +struct QScriptItem; +/// Internal QTextItem +class QTextItemInt : public QTextItem +{ +public: + inline QTextItemInt() + : justified(false), underlineStyle(QTextCharFormat::NoUnderline), num_chars(0), chars(0), + logClusters(0), f(0), fontEngine(0) + {} + QTextItemInt(const QScriptItem &si, QFont *font, const QTextCharFormat &format = QTextCharFormat()); + QTextItemInt(const QGlyphLayout &g, QFont *font, const QChar *chars, int numChars, QFontEngine *fe); + + /// copy the structure items, adjusting the glyphs arrays to the right subarrays. + /// the width of the returned QTextItemInt is not adjusted, for speed reasons + QTextItemInt midItem(QFontEngine *fontEngine, int firstGlyphIndex, int numGlyphs) const; + + QFixed descent; + QFixed ascent; + QFixed width; + + RenderFlags flags; + bool justified; + QTextCharFormat::UnderlineStyle underlineStyle; + const QTextCharFormat charFormat; + int num_chars; + const QChar *chars; + const unsigned short *logClusters; + const QFont *f; + + QGlyphLayout glyphs; + QFontEngine *fontEngine; +}; + +inline bool qIsControlChar(ushort uc) +{ + return uc >= 0x200b && uc <= 0x206f + && (uc <= 0x200f /* ZW Space, ZWNJ, ZWJ, LRM and RLM */ + || (uc >= 0x2028 && uc <= 0x202f /* LS, PS, LRE, RLE, PDF, LRO, RLO, NNBSP */) + || uc >= 0x206a /* ISS, ASS, IAFS, AFS, NADS, NODS */); +} + +struct Q_AUTOTEST_EXPORT QScriptItem +{ + inline QScriptItem() + : position(0), + num_glyphs(0), descent(-1), ascent(-1), leading(-1), width(-1), + glyph_data_offset(0) {} + inline QScriptItem(int p, const QScriptAnalysis &a) + : position(p), analysis(a), + num_glyphs(0), descent(-1), ascent(-1), leading(-1), width(-1), + glyph_data_offset(0) {} + + int position; + QScriptAnalysis analysis; + unsigned short num_glyphs; + QFixed descent; + QFixed ascent; + QFixed leading; + QFixed width; + int glyph_data_offset; + QFixed height() const { return ascent + descent + 1; } +}; + + +Q_DECLARE_TYPEINFO(QScriptItem, Q_MOVABLE_TYPE); + +typedef QVector QScriptItemArray; + +struct Q_AUTOTEST_EXPORT QScriptLine +{ + // created and filled in QTextLine::layout_helper + QScriptLine() + : from(0), length(0), + justified(0), gridfitted(0), + hasTrailingSpaces(0), leadingIncluded(0) {} + QFixed descent; + QFixed ascent; + QFixed leading; + QFixed x; + QFixed y; + QFixed width; + QFixed textWidth; + QFixed textAdvance; + int from; + signed int length : 28; + mutable uint justified : 1; + mutable uint gridfitted : 1; + uint hasTrailingSpaces : 1; + uint leadingIncluded : 1; + QFixed height() const { return (ascent + descent).ceil() + 1 + + (leadingIncluded? qMax(QFixed(),leading) : QFixed()); } + QFixed base() const { return ascent + + (leadingIncluded ? qMax(QFixed(),leading) : QFixed()); } + void setDefaultHeight(QTextEngine *eng); + void operator+=(const QScriptLine &other); +}; +Q_DECLARE_TYPEINFO(QScriptLine, Q_PRIMITIVE_TYPE); + + +inline void QScriptLine::operator+=(const QScriptLine &other) +{ + leading= qMax(leading + ascent, other.leading + other.ascent) - qMax(ascent, other.ascent); + descent = qMax(descent, other.descent); + ascent = qMax(ascent, other.ascent); + textWidth += other.textWidth; + length += other.length; +} + +typedef QVector QScriptLineArray; + +class QFontPrivate; +class QTextFormatCollection; + +class Q_GUI_EXPORT QTextEngine { +public: + enum LayoutState { + LayoutEmpty, + InLayout, + LayoutFailed, + }; + struct LayoutData { + LayoutData(const QString &str, void **stack_memory, int mem_size); + LayoutData(); + ~LayoutData(); + mutable QScriptItemArray items; + int allocated; + int available_glyphs; + void **memory; + unsigned short *logClustersPtr; + QGlyphLayout glyphLayout; + mutable int used; + uint hasBidi : 1; + uint layoutState : 2; + uint memory_on_stack : 1; + uint haveCharAttributes : 1; + QString string; + bool reallocate(int totalGlyphs); + }; + + QTextEngine(LayoutData *data); + QTextEngine(); + QTextEngine(const QString &str, const QFont &f); + ~QTextEngine(); + + enum Mode { + WidthOnly = 0x07 + }; + + // keep in sync with QAbstractFontEngine::TextShapingFlag!! + enum ShaperFlag { + RightToLeft = 0x0001, + DesignMetrics = 0x0002, + GlyphIndicesOnly = 0x0004 + }; + Q_DECLARE_FLAGS(ShaperFlags, ShaperFlag) + + void invalidate(); + void clearLineData(); + + void validate() const; + void itemize() const; + + bool isRightToLeft() const; + static void bidiReorder(int numRuns, const quint8 *levels, int *visualOrder); + + const HB_CharAttributes *attributes() const; + + void shape(int item) const; + + void justify(const QScriptLine &si); + + QFixed width(int charFrom, int numChars) const; + glyph_metrics_t boundingBox(int from, int len) const; + glyph_metrics_t tightBoundingBox(int from, int len) const; + + int length(int item) const { + const QScriptItem &si = layoutData->items[item]; + int from = si.position; + item++; + return (item < layoutData->items.size() ? layoutData->items[item].position : layoutData->string.length()) - from; + } + int length(const QScriptItem *si) const { + int end; + if (si + 1 < layoutData->items.constData()+ layoutData->items.size()) + end = (si+1)->position; + else + end = layoutData->string.length(); + return end - si->position; + } + + QFontEngine *fontEngine(const QScriptItem &si, QFixed *ascent = 0, QFixed *descent = 0, QFixed *leading = 0) const; + QFont font(const QScriptItem &si) const; + inline QFont font() const { return fnt; } + + /** + * Returns a pointer to an array of log clusters, offset at the script item. + * Each item in the array is a unsigned short. For each character in the original string there is an entry in the table + * so there is a one to one correlation in indexes between the original text and the index in the logcluster. + * The value of each item is the position in the glyphs array. Multiple similar pointers in the logclusters array imply + * that one glyph is used for more than one character. + * \sa glyphs() + */ + inline unsigned short *logClusters(const QScriptItem *si) const + { return layoutData->logClustersPtr+si->position; } + /** + * Returns an array of QGlyphLayout items, offset at the script item. + * Each item in the array matches one glyph in the text, storing the advance, position etc. + * The returned item's length equals to the number of available glyphs. This may be more + * than what was actually shaped. + * \sa logClusters() + */ + inline QGlyphLayout availableGlyphs(const QScriptItem *si) const { + return layoutData->glyphLayout.mid(si->glyph_data_offset); + } + /** + * Returns an array of QGlyphLayout items, offset at the script item. + * Each item in the array matches one glyph in the text, storing the advance, position etc. + * The returned item's length equals to the number of shaped glyphs. + * \sa logClusters() + */ + inline QGlyphLayout shapedGlyphs(const QScriptItem *si) const { + return layoutData->glyphLayout.mid(si->glyph_data_offset, si->num_glyphs); + } + + inline bool ensureSpace(int nGlyphs) const { + if (layoutData->glyphLayout.numGlyphs - layoutData->used < nGlyphs) + return layoutData->reallocate((((layoutData->used + nGlyphs)*3/2 + 15) >> 4) << 4); + return true; + } + + void freeMemory(); + + int findItem(int strPos) const; + inline QTextFormatCollection *formats() const { +#ifdef QT_BUILD_COMPAT_LIB + return 0; // Compat should never reference this symbol +#else + return block.docHandle()->formatCollection(); +#endif + } + QTextCharFormat format(const QScriptItem *si) const; + inline QAbstractTextDocumentLayout *docLayout() const { +#ifdef QT_BUILD_COMPAT_LIB + return 0; // Compat should never reference this symbol +#else + return block.docHandle()->document()->documentLayout(); +#endif + } + int formatIndex(const QScriptItem *si) const; + + /// returns the width of tab at index (in the tabs array) with the tab-start at position x + QFixed calculateTabWidth(int index, QFixed x) const; + + mutable QScriptLineArray lines; + + struct FontEngineCache { + FontEngineCache(); + mutable QFontEngine *prevFontEngine; + mutable QFontEngine *prevScaledFontEngine; + mutable int prevScript; + mutable int prevPosition; + mutable int prevLength; + inline void reset() { + prevFontEngine = 0; + prevScaledFontEngine = 0; + prevScript = -1; + prevPosition = -1; + prevLength = -1; + } + }; + mutable FontEngineCache feCache; + + QString text; + QFont fnt; + QTextBlock block; + + QTextOption option; + + QFixed minWidth; + QFixed maxWidth; + QPointF position; + uint ignoreBidi : 1; + uint cacheGlyphs : 1; + uint stackEngine : 1; + uint forceJustification : 1; + + int *underlinePositions; + + mutable LayoutData *layoutData; + + inline bool hasFormats() const { return (block.docHandle() || specialData); } + + struct SpecialData { + int preeditPosition; + QString preeditText; + QList addFormats; + QVector addFormatIndices; + QVector resolvedFormatIndices; + }; + SpecialData *specialData; + + bool atWordSeparator(int position) const; + bool atSpace(int position) const; + void indexAdditionalFormats(); + + QString elidedText(Qt::TextElideMode mode, const QFixed &width, int flags = 0) const; + + void shapeLine(const QScriptLine &line); + QFixed leadingSpaceWidth(const QScriptLine &line); + +private: + void setBoundary(int strPos) const; + void addRequiredBoundaries() const; + void shapeText(int item) const; + void shapeTextWithHarfbuzz(int item) const; +#if defined(Q_WS_WINCE) + void shapeTextWithCE(int item) const; +#endif +#if defined(Q_WS_MAC) + void shapeTextMac(int item) const; +#endif + void splitItem(int item, int pos) const; + + void resolveAdditionalFormats() const; +}; + +class QStackTextEngine : public QTextEngine { +public: + enum { MemSize = 256*40/sizeof(void *) }; + QStackTextEngine(const QString &string, const QFont &f); + LayoutData _layoutData; + void *_memory[MemSize]; +}; + + +Q_DECLARE_OPERATORS_FOR_FLAGS(QTextEngine::ShaperFlags) + +QT_END_NAMESPACE + +#endif // QTEXTENGINE_P_H diff --git a/src/gui/text/qtextformat.cpp b/src/gui/text/qtextformat.cpp new file mode 100644 index 0000000000..a02ea49f67 --- /dev/null +++ b/src/gui/text/qtextformat.cpp @@ -0,0 +1,3297 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextformat.h" +#include "qtextformat_p.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QTextLength + \reentrant + + \brief The QTextLength class encapsulates the different types of length + used in a QTextDocument. + + \ingroup richtext-processing + + When we specify a value for the length of an element in a text document, + we often need to provide some other information so that the length is + used in the way we expect. For example, when we specify a table width, + the value can represent a fixed number of pixels, or it can be a percentage + value. This information changes both the meaning of the value and the way + it is used. + + Generally, this class is used to specify table widths. These can be + specified either as a fixed amount of pixels, as a percentage of the + containing frame's width, or by a variable width that allows it to take + up just the space it requires. + + \sa QTextTable +*/ + +/*! + \fn explicit QTextLength::QTextLength() + + Constructs a new length object which represents a variable size. +*/ + +/*! + \fn QTextLength::QTextLength(Type type, qreal value) + + Constructs a new length object of the given \a type and \a value. +*/ + +/*! + \fn Type QTextLength::type() const + + Returns the type of this length object. + + \sa QTextLength::Type +*/ + +/*! + \fn qreal QTextLength::value(qreal maximumLength) const + + Returns the effective length, constrained by the type of the length object + and the specified \a maximumLength. + + \sa type() +*/ + +/*! + \fn qreal QTextLength::rawValue() const + + Returns the constraint value that is specific for the type of the length. + If the length is QTextLength::PercentageLength then the raw value is in + percent, in the range of 0 to 100. If the length is QTextLength::FixedLength + then that fixed amount is returned. For variable lengths, zero is returned. +*/ + +/*! + \fn bool QTextLength::operator==(const QTextLength &other) const + + Returns true if this text length is the same as the \a other text + length. +*/ + +/*! + \fn bool QTextLength::operator!=(const QTextLength &other) const + + Returns true if this text length is different from the \a other text + length. +*/ + +/*! + \enum QTextLength::Type + + This enum describes the different types a length object can + have. + + \value VariableLength The width of the object is variable + \value FixedLength The width of the object is fixed + \value PercentageLength The width of the object is in + percentage of the maximum width + + \sa type() +*/ + +/*! + Returns the text length as a QVariant +*/ +QTextLength::operator QVariant() const +{ + return QVariant(QVariant::TextLength, this); +} + +#ifndef QT_NO_DATASTREAM +QDataStream &operator<<(QDataStream &stream, const QTextLength &length) +{ + return stream << qint32(length.lengthType) << double(length.fixedValueOrPercentage); +} + +QDataStream &operator>>(QDataStream &stream, QTextLength &length) +{ + qint32 type; + double fixedValueOrPercentage; + stream >> type >> fixedValueOrPercentage; + length.fixedValueOrPercentage = fixedValueOrPercentage; + length.lengthType = QTextLength::Type(type); + return stream; +} +#endif // QT_NO_DATASTREAM + +class QTextFormatPrivate : public QSharedData +{ +public: + QTextFormatPrivate() : hashDirty(true), fontDirty(true), hashValue(0) {} + + struct Property + { + inline Property(qint32 k, const QVariant &v) : key(k), value(v) {} + inline Property() {} + + qint32 key; + QVariant value; + + inline bool operator==(const Property &other) const + { return key == other.key && value == other.value; } + inline bool operator!=(const Property &other) const + { return key != other.key || value != other.value; } + }; + + inline uint hash() const + { + if (!hashDirty) + return hashValue; + return recalcHash(); + } + + inline bool operator==(const QTextFormatPrivate &rhs) const { + if (hash() != rhs.hash()) + return false; + + return props == rhs.props; + } + + inline void insertProperty(qint32 key, const QVariant &value) + { + hashDirty = true; + if (key >= QTextFormat::FirstFontProperty && key <= QTextFormat::LastFontProperty) + fontDirty = true; + for (int i = 0; i < props.count(); ++i) + if (props.at(i).key == key) { + props[i].value = value; + return; + } + props.append(Property(key, value)); + } + + inline void clearProperty(qint32 key) + { + for (int i = 0; i < props.count(); ++i) + if (props.at(i).key == key) { + hashDirty = true; + if (key >= QTextFormat::FirstFontProperty && key <= QTextFormat::LastFontProperty) + fontDirty = true; + props.remove(i); + return; + } + } + + inline int propertyIndex(qint32 key) const + { + for (int i = 0; i < props.count(); ++i) + if (props.at(i).key == key) + return i; + return -1; + } + + inline QVariant property(qint32 key) const + { + const int idx = propertyIndex(key); + if (idx < 0) + return QVariant(); + return props.at(idx).value; + } + + inline bool hasProperty(qint32 key) const + { return propertyIndex(key) != -1; } + + void resolveFont(const QFont &defaultFont); + + inline const QFont &font() const { + if (fontDirty) + recalcFont(); + return fnt; + } + + QVector props; +private: + + uint recalcHash() const; + void recalcFont() const; + + mutable bool hashDirty; + mutable bool fontDirty; + mutable uint hashValue; + mutable QFont fnt; + + friend QDataStream &operator<<(QDataStream &, const QTextFormat &); + friend QDataStream &operator>>(QDataStream &, QTextFormat &); +}; + +// this is only safe because sizeof(int) == sizeof(float) +static inline uint hash(float d) +{ +#ifdef Q_CC_GNU + // this is a GCC extension and isn't guaranteed to work in other compilers + // the reinterpret_cast below generates a strict-aliasing warning with GCC + union { float f; uint u; } cvt; + cvt.f = d; + return cvt.u; +#else + return reinterpret_cast(d); +#endif +} + +static inline uint hash(const QColor &color) +{ + return (color.isValid()) ? color.rgba() : 0x234109; +} + +static inline uint hash(const QPen &pen) +{ + return hash(pen.color()) + hash(pen.widthF()); +} + +static inline uint hash(const QBrush &brush) +{ + return hash(brush.color()) + (brush.style() << 3); +} + +static inline uint variantHash(const QVariant &variant) +{ + // simple and fast hash functions to differentiate between type and value + switch (variant.userType()) { // sorted by occurrence frequency + case QVariant::String: return qHash(variant.toString()); + case QVariant::Double: return hash(variant.toDouble()); + case QVariant::Int: return 0x811890 + variant.toInt(); + case QVariant::Brush: + return 0x01010101 + hash(qvariant_cast(variant)); + case QVariant::Bool: return 0x371818 + variant.toBool(); + case QVariant::Pen: return 0x02020202 + hash(qvariant_cast(variant)); + case QVariant::List: + return 0x8377 + qvariant_cast(variant).count(); + case QVariant::Color: return hash(qvariant_cast(variant)); + case QVariant::TextLength: + return 0x377 + hash(qvariant_cast(variant).rawValue()); + case QMetaType::Float: return hash(variant.toFloat()); + case QVariant::Invalid: return 0; + default: break; + } + return qHash(variant.typeName()); +} + +static inline int getHash(const QTextFormatPrivate *d, int format) +{ + return (d ? d->hash() : 0) + format; +} + +uint QTextFormatPrivate::recalcHash() const +{ + hashValue = 0; + for (QVector::ConstIterator it = props.constBegin(); it != props.constEnd(); ++it) + hashValue += (it->key << 16) + variantHash(it->value); + + hashDirty = false; + + return hashValue; +} + +void QTextFormatPrivate::resolveFont(const QFont &defaultFont) +{ + recalcFont(); + const uint oldMask = fnt.resolve(); + fnt = fnt.resolve(defaultFont); + + if (hasProperty(QTextFormat::FontSizeAdjustment)) { + const qreal scaleFactors[7] = {qreal(0.7), qreal(0.8), qreal(1.0), qreal(1.2), qreal(1.5), qreal(2), qreal(2.4)}; + + const int htmlFontSize = qBound(0, property(QTextFormat::FontSizeAdjustment).toInt() + 3 - 1, 6); + + + if (defaultFont.pointSize() <= 0) { + qreal pixelSize = scaleFactors[htmlFontSize] * defaultFont.pixelSize(); + fnt.setPixelSize(qRound(pixelSize)); + } else { + qreal pointSize = scaleFactors[htmlFontSize] * defaultFont.pointSizeF(); + fnt.setPointSizeF(pointSize); + } + } + + fnt.resolve(oldMask); +} + +void QTextFormatPrivate::recalcFont() const +{ + // update cached font as well + QFont f; + + for (int i = 0; i < props.count(); ++i) { + switch (props.at(i).key) { + case QTextFormat::FontFamily: + f.setFamily(props.at(i).value.toString()); + break; + case QTextFormat::FontPointSize: + f.setPointSizeF(props.at(i).value.toReal()); + break; + case QTextFormat::FontPixelSize: + f.setPixelSize(props.at(i).value.toInt()); + break; + case QTextFormat::FontWeight: { + int weight = props.at(i).value.toInt(); + if (weight == 0) weight = QFont::Normal; + f.setWeight(weight); + break; } + case QTextFormat::FontItalic: + f.setItalic(props.at(i).value.toBool()); + break; + case QTextFormat::FontUnderline: + if (! hasProperty(QTextFormat::TextUnderlineStyle)) // don't use the old one if the new one is there. + f.setUnderline(props.at(i).value.toBool()); + break; + case QTextFormat::TextUnderlineStyle: + f.setUnderline(static_cast(props.at(i).value.toInt()) == QTextCharFormat::SingleUnderline); + break; + case QTextFormat::FontOverline: + f.setOverline(props.at(i).value.toBool()); + break; + case QTextFormat::FontStrikeOut: + f.setStrikeOut(props.at(i).value.toBool()); + break; + case QTextFormat::FontLetterSpacing: + f.setLetterSpacing(QFont::PercentageSpacing, props.at(i).value.toReal()); + break; + case QTextFormat::FontWordSpacing: + f.setWordSpacing(props.at(i).value.toReal()); + break; + case QTextFormat::FontCapitalization: + f.setCapitalization(static_cast (props.at(i).value.toInt())); + break; + case QTextFormat::FontFixedPitch: { + const bool value = props.at(i).value.toBool(); + if (f.fixedPitch() != value) + f.setFixedPitch(value); + break; } + case QTextFormat::FontStyleHint: + f.setStyleHint(static_cast(props.at(i).value.toInt()), f.styleStrategy()); + break; + case QTextFormat::FontHintingPreference: + f.setHintingPreference(static_cast(props.at(i).value.toInt())); + break; + case QTextFormat::FontStyleStrategy: + f.setStyleStrategy(static_cast(props.at(i).value.toInt())); + break; + case QTextFormat::FontKerning: + f.setKerning(props.at(i).value.toBool()); + break; + default: + break; + } + } + fnt = f; + fontDirty = false; +} + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &stream, const QTextFormat &fmt) +{ + stream << fmt.format_type << fmt.properties(); + return stream; +} + +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextFormat &fmt) +{ + QMap properties; + stream >> fmt.format_type >> properties; + + // QTextFormat's default constructor doesn't allocate the private structure, so + // we have to do this, in case fmt is a default constructed value. + if(!fmt.d) + fmt.d = new QTextFormatPrivate(); + + for (QMap::ConstIterator it = properties.constBegin(); + it != properties.constEnd(); ++it) + fmt.d->insertProperty(it.key(), it.value()); + + return stream; +} +#endif // QT_NO_DATASTREAM + +/*! + \class QTextFormat + \reentrant + + \brief The QTextFormat class provides formatting information for a + QTextDocument. + + \ingroup richtext-processing + \ingroup shared + + A QTextFormat is a generic class used for describing the format of + parts of a QTextDocument. The derived classes QTextCharFormat, + QTextBlockFormat, QTextListFormat, and QTextTableFormat are usually + more useful, and describe the formatting that is applied to + specific parts of the document. + + A format has a \c FormatType which specifies the kinds of text item it + can format; e.g. a block of text, a list, a table, etc. A format + also has various properties (some specific to particular format + types), as described by the Property enum. Every property has a + corresponding Property. + + The format type is given by type(), and the format can be tested + with isCharFormat(), isBlockFormat(), isListFormat(), + isTableFormat(), isFrameFormat(), and isImageFormat(). If the + type is determined, it can be retrieved with toCharFormat(), + toBlockFormat(), toListFormat(), toTableFormat(), toFrameFormat(), + and toImageFormat(). + + A format's properties can be set with the setProperty() functions, + and retrieved with boolProperty(), intProperty(), doubleProperty(), + and stringProperty() as appropriate. All the property IDs used in + the format can be retrieved with allPropertyIds(). One format can + be merged into another using merge(). + + A format's object index can be set with setObjectIndex(), and + retrieved with objectIndex(). These methods can be used to + associate the format with a QTextObject. It is used to represent + lists, frames, and tables inside the document. + + \sa {Rich Text Processing} +*/ + +/*! + \enum QTextFormat::FormatType + + This enum describes the text item a QTextFormat object is formatting. + + \value InvalidFormat An invalid format as created by the default + constructor + \value BlockFormat The object formats a text block + \value CharFormat The object formats a single character + \value ListFormat The object formats a list + \value TableFormat The object formats a table + \value FrameFormat The object formats a frame + + \value UserFormat + + \sa QTextCharFormat, QTextBlockFormat, QTextListFormat, + QTextTableFormat, type() +*/ + +/*! + \enum QTextFormat::Property + + This enum describes the different properties a format can have. + + \value ObjectIndex The index of the formatted object. See objectIndex(). + + Paragraph and character properties + + \value CssFloat How a frame is located relative to the surrounding text + \value LayoutDirection The layout direction of the text in the document + (Qt::LayoutDirection). + + \value OutlinePen + \value ForegroundBrush + \value BackgroundBrush + \value BackgroundImageUrl + + Paragraph properties + + \value BlockAlignment + \value BlockTopMargin + \value BlockBottomMargin + \value BlockLeftMargin + \value BlockRightMargin + \value TextIndent + \value TabPositions Specifies the tab positions. The tab positions are structs of QTextOption::Tab which are stored in + a QList (internally, in a QList). + \value BlockIndent + \value LineHeight + \value LineHeightType + \value BlockNonBreakableLines + \value BlockTrailingHorizontalRulerWidth The width of a horizontal ruler element. + + Character properties + + \value FontFamily + \value FontPointSize + \value FontPixelSize + \value FontSizeAdjustment Specifies the change in size given to the fontsize already set using + FontPointSize or FontPixelSize. + \value FontFixedPitch + \omitvalue FontSizeIncrement + \value FontWeight + \value FontItalic + \value FontUnderline \e{This property has been deprecated.} Use QTextFormat::TextUnderlineStyle instead. + \value FontOverline + \value FontStrikeOut + \value FontCapitalization Specifies the capitalization type that is to be applied to the text. + \value FontLetterSpacing Changes the default spacing between individual letters in the font. The value is + specified in percentage, with 100 as the default value. + \value FontWordSpacing Changes the default spacing between individual words. A positive value increases the word spacing + by the corresponding pixels; a negative value decreases the spacing. + \value FontStyleHint Corresponds to the QFont::StyleHint property + \value FontStyleStrategy Corresponds to the QFont::StyleStrategy property + \value FontKerning Specifies whether the font has kerning turned on. + + \omitvalue FirstFontProperty + \omitvalue LastFontProperty + + \value TextUnderlineColor + \value TextVerticalAlignment + \value TextOutline + \value TextUnderlineStyle + \value TextToolTip Specifies the (optional) tool tip to be displayed for a fragment of text. + + \value IsAnchor + \value AnchorHref + \value AnchorName + \value ObjectType + + List properties + + \value ListStyle + \value ListIndent + + Table and frame properties + + \value FrameBorder + \value FrameBorderBrush + \value FrameBorderStyle See the \l{QTextFrameFormat::BorderStyle}{BorderStyle} enum. + \value FrameBottomMargin + \value FrameHeight + \value FrameLeftMargin + \value FrameMargin + \value FramePadding + \value FrameRightMargin + \value FrameTopMargin + \value FrameWidth + \value TableCellSpacing + \value TableCellPadding + \value TableColumns + \value TableColumnWidthConstraints + \value TableHeaderRowCount + + Table cell properties + + \value TableCellRowSpan + \value TableCellColumnSpan + \value TableCellLeftPadding + \value TableCellRightPadding + \value TableCellTopPadding + \value TableCellBottomPadding + + Image properties + + \value ImageName + \value ImageWidth + \value ImageHeight + + Selection properties + + \value FullWidthSelection When set on the characterFormat of a selection, + the whole width of the text will be shown selected. + + Page break properties + + \value PageBreakPolicy Specifies how pages are broken. See the PageBreakFlag enum. + + \value UserProperty + + \sa property(), setProperty() +*/ + +/*! + \enum QTextFormat::ObjectTypes + + This enum describes what kind of QTextObject this format is associated with. + + \value NoObject + \value ImageObject + \value TableObject + \value TableCellObject + \value UserObject The first object that can be used for application-specific purposes. + + \sa QTextObject, QTextTable, QTextObject::format() +*/ + +/*! + \enum QTextFormat::PageBreakFlag + \since 4.2 + + This enum describes how page breaking is performed when printing. It maps to the + corresponding css properties. + + \value PageBreak_Auto The page break is determined automatically depending on the + available space on the current page + \value PageBreak_AlwaysBefore The page is always broken before the paragraph/table + \value PageBreak_AlwaysAfter A new page is always started after the paragraph/table + + \sa QTextBlockFormat::pageBreakPolicy(), QTextFrameFormat::pageBreakPolicy(), + PageBreakPolicy +*/ + +/*! + \fn bool QTextFormat::isValid() const + + Returns true if the format is valid (i.e. is not + InvalidFormat); otherwise returns false. +*/ + +/*! + \fn bool QTextFormat::isCharFormat() const + + Returns true if this text format is a \c CharFormat; otherwise + returns false. +*/ + + +/*! + \fn bool QTextFormat::isBlockFormat() const + + Returns true if this text format is a \c BlockFormat; otherwise + returns false. +*/ + + +/*! + \fn bool QTextFormat::isListFormat() const + + Returns true if this text format is a \c ListFormat; otherwise + returns false. +*/ + + +/*! + \fn bool QTextFormat::isTableFormat() const + + Returns true if this text format is a \c TableFormat; otherwise + returns false. +*/ + + +/*! + \fn bool QTextFormat::isFrameFormat() const + + Returns true if this text format is a \c FrameFormat; otherwise + returns false. +*/ + + +/*! + \fn bool QTextFormat::isImageFormat() const + + Returns true if this text format is an image format; otherwise + returns false. +*/ + + +/*! + \fn bool QTextFormat::isTableCellFormat() const + \since 4.4 + + Returns true if this text format is a \c TableCellFormat; otherwise + returns false. +*/ + + +/*! + Creates a new text format with an \c InvalidFormat. + + \sa FormatType +*/ +QTextFormat::QTextFormat() + : format_type(InvalidFormat) +{ +} + +/*! + Creates a new text format of the given \a type. + + \sa FormatType +*/ +QTextFormat::QTextFormat(int type) + : format_type(type) +{ +} + + +/*! + \fn QTextFormat::QTextFormat(const QTextFormat &other) + + Creates a new text format with the same attributes as the \a other + text format. +*/ +QTextFormat::QTextFormat(const QTextFormat &rhs) + : d(rhs.d), format_type(rhs.format_type) +{ +} + +/*! + \fn QTextFormat &QTextFormat::operator=(const QTextFormat &other) + + Assigns the \a other text format to this text format, and returns a + reference to this text format. +*/ +QTextFormat &QTextFormat::operator=(const QTextFormat &rhs) +{ + d = rhs.d; + format_type = rhs.format_type; + return *this; +} + +/*! + Destroys this text format. +*/ +QTextFormat::~QTextFormat() +{ +} + + +/*! + Returns the text format as a QVariant +*/ +QTextFormat::operator QVariant() const +{ + return QVariant(QVariant::TextFormat, this); +} + +/*! + Merges the \a other format with this format; where there are + conflicts the \a other format takes precedence. +*/ +void QTextFormat::merge(const QTextFormat &other) +{ + if (format_type != other.format_type) + return; + + if (!d) { + d = other.d; + return; + } + + if (!other.d) + return; + + QTextFormatPrivate *d = this->d; + + const QVector &otherProps = other.d->props; + d->props.reserve(d->props.size() + otherProps.size()); + for (int i = 0; i < otherProps.count(); ++i) { + const QTextFormatPrivate::Property &p = otherProps.at(i); + d->insertProperty(p.key, p.value); + } +} + +/*! + Returns the type of this format. + + \sa FormatType +*/ +int QTextFormat::type() const +{ + return format_type; +} + +/*! + Returns this format as a block format. +*/ +QTextBlockFormat QTextFormat::toBlockFormat() const +{ + return QTextBlockFormat(*this); +} + +/*! + Returns this format as a character format. +*/ +QTextCharFormat QTextFormat::toCharFormat() const +{ + return QTextCharFormat(*this); +} + +/*! + Returns this format as a list format. +*/ +QTextListFormat QTextFormat::toListFormat() const +{ + return QTextListFormat(*this); +} + +/*! + Returns this format as a table format. +*/ +QTextTableFormat QTextFormat::toTableFormat() const +{ + return QTextTableFormat(*this); +} + +/*! + Returns this format as a frame format. +*/ +QTextFrameFormat QTextFormat::toFrameFormat() const +{ + return QTextFrameFormat(*this); +} + +/*! + Returns this format as an image format. +*/ +QTextImageFormat QTextFormat::toImageFormat() const +{ + return QTextImageFormat(*this); +} + +/*! + \since 4.4 + + Returns this format as a table cell format. +*/ +QTextTableCellFormat QTextFormat::toTableCellFormat() const +{ + return QTextTableCellFormat(*this); +} + +/*! + Returns the value of the property specified by \a propertyId. If the + property isn't of QTextFormat::Bool type, false is returned instead. + + \sa setProperty() intProperty() doubleProperty() stringProperty() colorProperty() lengthProperty() lengthVectorProperty() Property +*/ +bool QTextFormat::boolProperty(int propertyId) const +{ + if (!d) + return false; + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::Bool) + return false; + return prop.toBool(); +} + +/*! + Returns the value of the property specified by \a propertyId. If the + property is not of QTextFormat::Integer type, 0 is returned instead. + + \sa setProperty() boolProperty() doubleProperty() stringProperty() colorProperty() lengthProperty() lengthVectorProperty() Property +*/ +int QTextFormat::intProperty(int propertyId) const +{ + // required, since the default layout direction has to be LayoutDirectionAuto, which is not integer 0 + int def = (propertyId == QTextFormat::LayoutDirection) ? int(Qt::LayoutDirectionAuto) : 0; + + if (!d) + return def; + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::Int) + return def; + return prop.toInt(); +} + +/*! + Returns the value of the property specified by \a propertyId. If the + property isn't of QVariant::Double or QMetaType::Float type, 0 is + returned instead. + + \sa setProperty() boolProperty() intProperty() stringProperty() colorProperty() lengthProperty() lengthVectorProperty() Property +*/ +qreal QTextFormat::doubleProperty(int propertyId) const +{ + if (!d) + return 0.; + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::Double && prop.userType() != QMetaType::Float) + return 0.; + return qvariant_cast(prop); +} + +/*! + Returns the value of the property given by \a propertyId; if the + property isn't of QVariant::String type, an empty string is + returned instead. + + \sa setProperty() boolProperty() intProperty() doubleProperty() colorProperty() lengthProperty() lengthVectorProperty() Property +*/ +QString QTextFormat::stringProperty(int propertyId) const +{ + if (!d) + return QString(); + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::String) + return QString(); + return prop.toString(); +} + +/*! + Returns the value of the property given by \a propertyId; if the + property isn't of QVariant::Color type, an invalid color is + returned instead. + + \sa setProperty(), boolProperty(), intProperty(), doubleProperty(), + stringProperty(), lengthProperty(), lengthVectorProperty(), Property +*/ +QColor QTextFormat::colorProperty(int propertyId) const +{ + if (!d) + return QColor(); + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::Color) + return QColor(); + return qvariant_cast(prop); +} + +/*! + Returns the value of the property given by \a propertyId; if the + property isn't of QVariant::Pen type, Qt::NoPen is + returned instead. + + \sa setProperty() boolProperty() intProperty() doubleProperty() stringProperty() lengthProperty() lengthVectorProperty() Property +*/ +QPen QTextFormat::penProperty(int propertyId) const +{ + if (!d) + return QPen(Qt::NoPen); + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::Pen) + return QPen(Qt::NoPen); + return qvariant_cast(prop); +} + +/*! + Returns the value of the property given by \a propertyId; if the + property isn't of QVariant::Brush type, Qt::NoBrush is + returned instead. + + \sa setProperty() boolProperty() intProperty() doubleProperty() stringProperty() lengthProperty() lengthVectorProperty() Property +*/ +QBrush QTextFormat::brushProperty(int propertyId) const +{ + if (!d) + return QBrush(Qt::NoBrush); + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::Brush) + return QBrush(Qt::NoBrush); + return qvariant_cast(prop); +} + +/*! + Returns the value of the property given by \a propertyId. + + \sa setProperty() boolProperty() intProperty() doubleProperty() stringProperty() colorProperty() lengthVectorProperty() Property +*/ +QTextLength QTextFormat::lengthProperty(int propertyId) const +{ + if (!d) + return QTextLength(); + return qvariant_cast(d->property(propertyId)); +} + +/*! + Returns the value of the property given by \a propertyId. If the + property isn't of QTextFormat::LengthVector type, an empty length + vector is returned instead. + + \sa setProperty() boolProperty() intProperty() doubleProperty() stringProperty() colorProperty() lengthProperty() Property +*/ +QVector QTextFormat::lengthVectorProperty(int propertyId) const +{ + QVector vector; + if (!d) + return vector; + const QVariant prop = d->property(propertyId); + if (prop.userType() != QVariant::List) + return vector; + + QList propertyList = prop.toList(); + for (int i=0; i(var)); + } + + return vector; +} + +/*! + Returns the property specified by the given \a propertyId. + + \sa Property +*/ +QVariant QTextFormat::property(int propertyId) const +{ + return d ? d->property(propertyId) : QVariant(); +} + +/*! + Sets the property specified by the \a propertyId to the given \a value. + + \sa Property +*/ +void QTextFormat::setProperty(int propertyId, const QVariant &value) +{ + if (!d) + d = new QTextFormatPrivate; + if (!value.isValid()) + clearProperty(propertyId); + else + d->insertProperty(propertyId, value); +} + +/*! + Sets the value of the property given by \a propertyId to \a value. + + \sa lengthVectorProperty() Property +*/ +void QTextFormat::setProperty(int propertyId, const QVector &value) +{ + if (!d) + d = new QTextFormatPrivate; + QVariantList list; + for (int i=0; iinsertProperty(propertyId, list); +} + +/*! + Clears the value of the property given by \a propertyId + + \sa Property +*/ +void QTextFormat::clearProperty(int propertyId) +{ + if (!d) + return; + d->clearProperty(propertyId); +} + + +/*! + \fn void QTextFormat::setObjectType(int type) + + Sets the text format's object type to \a type. + + \sa ObjectTypes, objectType() +*/ + + +/*! + \fn int QTextFormat::objectType() const + + Returns the text format's object type. + + \sa ObjectTypes, setObjectType() +*/ + + +/*! + Returns the index of the format object, or -1 if the format object is invalid. + + \sa setObjectIndex() +*/ +int QTextFormat::objectIndex() const +{ + if (!d) + return -1; + const QVariant prop = d->property(ObjectIndex); + if (prop.userType() != QVariant::Int) // #### + return -1; + return prop.toInt(); +} + +/*! + \fn void QTextFormat::setObjectIndex(int index) + + Sets the format object's object \a index. + + \sa objectIndex() +*/ +void QTextFormat::setObjectIndex(int o) +{ + if (o == -1) { + if (d) + d->clearProperty(ObjectIndex); + } else { + if (!d) + d = new QTextFormatPrivate; + // ### type + d->insertProperty(ObjectIndex, o); + } +} + +/*! + Returns true if the text format has a property with the given \a + propertyId; otherwise returns false. + + \sa properties() Property +*/ +bool QTextFormat::hasProperty(int propertyId) const +{ + return d ? d->hasProperty(propertyId) : false; +} + +/* + Returns the property type for the given \a propertyId. + + \sa hasProperty() allPropertyIds() Property +*/ + +/*! + Returns a map with all properties of this text format. +*/ +QMap QTextFormat::properties() const +{ + QMap map; + if (d) { + for (int i = 0; i < d->props.count(); ++i) + map.insert(d->props.at(i).key, d->props.at(i).value); + } + return map; +} + +/*! + \since 4.3 + Returns the number of properties stored in the format. +*/ +int QTextFormat::propertyCount() const +{ + return d ? d->props.count() : 0; +} + +/*! + \fn bool QTextFormat::operator!=(const QTextFormat &other) const + + Returns true if this text format is different from the \a other text + format. +*/ + + +/*! + \fn bool QTextFormat::operator==(const QTextFormat &other) const + + Returns true if this text format is the same as the \a other text + format. +*/ +bool QTextFormat::operator==(const QTextFormat &rhs) const +{ + if (format_type != rhs.format_type) + return false; + + if (d == rhs.d) + return true; + + if (d && d->props.isEmpty() && !rhs.d) + return true; + + if (!d && rhs.d && rhs.d->props.isEmpty()) + return true; + + if (!d || !rhs.d) + return false; + + return *d == *rhs.d; +} + +/*! + \class QTextCharFormat + \reentrant + + \brief The QTextCharFormat class provides formatting information for + characters in a QTextDocument. + + \ingroup richtext-processing + + The character format of text in a document specifies the visual properties + of the text, as well as information about its role in a hypertext document. + + The font used can be set by supplying a font to the setFont() function, and + each aspect of its appearance can be adjusted to give the desired effect. + setFontFamily() and setFontPointSize() define the font's family (e.g. Times) + and printed size; setFontWeight() and setFontItalic() provide control over + the style of the font. setFontUnderline(), setFontOverline(), + setFontStrikeOut(), and setFontFixedPitch() provide additional effects for + text. + + The color is set with setForeground(). If the text is intended to be used + as an anchor (for hyperlinks), this can be enabled with setAnchor(). The + setAnchorHref() and setAnchorNames() functions are used to specify the + information about the hyperlink's destination and the anchor's name. + + \sa QTextFormat QTextBlockFormat QTextTableFormat QTextListFormat +*/ + +/*! + \enum QTextCharFormat::VerticalAlignment + + This enum describes the ways that adjacent characters can be vertically + aligned. + + \value AlignNormal Adjacent characters are positioned in the standard + way for text in the writing system in use. + \value AlignSuperScript Characters are placed above the baseline for + normal text. + \value AlignSubScript Characters are placed below the baseline for + normal text. + \value AlignMiddle The center of the object is vertically aligned with the base line. + Currently, this is only implemented for inline objects. + \value AlignBottom The bottom edge of the object is vertically aligned with + the base line. + \value AlignTop The top edge of the object is vertically aligned with + the base line. +*/ + +/*! + \enum QTextCharFormat::UnderlineStyle + + This enum describes the different ways drawing underlined text. + + \value NoUnderline Text is draw without any underlining decoration. + \value SingleUnderline A line is drawn using Qt::SolidLine. + \value DashUnderline Dashes are drawn using Qt::DashLine. + \value DotLine Dots are drawn using Qt::DotLine; + \value DashDotLine Dashs and dots are drawn using Qt::DashDotLine. + \value DashDotDotLine Underlines draw drawn using Qt::DashDotDotLine. + \value WaveUnderline The text is underlined using a wave shaped line. + \value SpellCheckUnderline The underline is drawn depending on the QStyle::SH_SpellCeckUnderlineStyle + style hint of the QApplication style. By default this is mapped to + WaveUnderline, on Mac OS X it is mapped to DashDotLine. + + \sa Qt::PenStyle +*/ + +/*! + \fn QTextCharFormat::QTextCharFormat() + + Constructs a new character format object. +*/ +QTextCharFormat::QTextCharFormat() : QTextFormat(CharFormat) {} + +/*! + \internal + \fn QTextCharFormat::QTextCharFormat(const QTextFormat &other) + + Creates a new character format with the same attributes as the \a given + text format. +*/ +QTextCharFormat::QTextCharFormat(const QTextFormat &fmt) + : QTextFormat(fmt) +{ +} + +/*! + \fn bool QTextCharFormat::isValid() const + + Returns true if this character format is valid; otherwise returns + false. +*/ + + +/*! + \fn void QTextCharFormat::setFontFamily(const QString &family) + + Sets the text format's font \a family. + + \sa setFont() +*/ + + +/*! + \fn QString QTextCharFormat::fontFamily() const + + Returns the text format's font family. + + \sa font() +*/ + + +/*! + \fn void QTextCharFormat::setFontPointSize(qreal size) + + Sets the text format's font \a size. + + \sa setFont() +*/ + + +/*! + \fn qreal QTextCharFormat::fontPointSize() const + + Returns the font size used to display text in this format. + + \sa font() +*/ + + +/*! + \fn void QTextCharFormat::setFontWeight(int weight) + + Sets the text format's font weight to \a weight. + + \sa setFont(), QFont::Weight +*/ + + +/*! + \fn int QTextCharFormat::fontWeight() const + + Returns the text format's font weight. + + \sa font(), QFont::Weight +*/ + + +/*! + \fn void QTextCharFormat::setFontItalic(bool italic) + + If \a italic is true, sets the text format's font to be italic; otherwise + the font will be non-italic. + + \sa setFont() +*/ + + +/*! + \fn bool QTextCharFormat::fontItalic() const + + Returns true if the text format's font is italic; otherwise + returns false. + + \sa font() +*/ + + +/*! + \fn void QTextCharFormat::setFontUnderline(bool underline) + + If \a underline is true, sets the text format's font to be underlined; + otherwise it is displayed non-underlined. + + \sa setFont() +*/ + + +/*! + \fn bool QTextCharFormat::fontUnderline() const + + Returns true if the text format's font is underlined; otherwise + returns false. + + \sa font() +*/ +bool QTextCharFormat::fontUnderline() const +{ + if (hasProperty(TextUnderlineStyle)) + return underlineStyle() == SingleUnderline; + return boolProperty(FontUnderline); +} + +/*! + \fn UnderlineStyle QTextCharFormat::underlineStyle() const + \since 4.2 + + Returns the style of underlining the text. +*/ + +/*! + \fn void QTextCharFormat::setUnderlineStyle(UnderlineStyle style) + \since 4.2 + + Sets the style of underlining the text to \a style. +*/ +void QTextCharFormat::setUnderlineStyle(UnderlineStyle style) +{ + setProperty(TextUnderlineStyle, style); + // for compatibility + setProperty(FontUnderline, style == SingleUnderline); +} + +/*! + \fn void QTextCharFormat::setFontOverline(bool overline) + + If \a overline is true, sets the text format's font to be overlined; + otherwise the font is displayed non-overlined. + + \sa setFont() +*/ + + +/*! + \fn bool QTextCharFormat::fontOverline() const + + Returns true if the text format's font is overlined; otherwise + returns false. + + \sa font() +*/ + + +/*! + \fn void QTextCharFormat::setFontStrikeOut(bool strikeOut) + + If \a strikeOut is true, sets the text format's font with strike-out + enabled (with a horizontal line through it); otherwise it is displayed + without strikeout. + + \sa setFont() +*/ + + +/*! + \fn bool QTextCharFormat::fontStrikeOut() const + + Returns true if the text format's font is struck out (has a horizontal line + drawn through it); otherwise returns false. + + \sa font() +*/ + + +/*! + \since 4.5 + \fn void QTextCharFormat::setFontStyleHint(QFont::StyleHint hint, QFont::StyleStrategy strategy) + + Sets the font style \a hint and \a strategy. + + Qt does not support style hints on X11 since this information is not provided by the window system. + + \sa setFont() + \sa QFont::setStyleHint() +*/ + + +/*! + \since 4.5 + \fn void QTextCharFormat::setFontStyleStrategy(QFont::StyleStrategy strategy) + + Sets the font style \a strategy. + + \sa setFont() + \sa QFont::setStyleStrategy() +*/ + + +/*! + \since 4.5 + \fn void QTextCharFormat::setFontKerning(bool enable) + Enables kerning for this font if \a enable is true; otherwise disables it. + + When kerning is enabled, glyph metrics do not add up anymore, even for + Latin text. In other words, the assumption that width('a') + width('b') + is equal to width("ab") is not neccesairly true. + + \sa setFont() +*/ + + +/*! + \fn QTextCharFormat::StyleHint QTextCharFormat::fontStyleHint() const + \since 4.5 + + Returns the font style hint. + + \sa setFontStyleHint(), font() +*/ + + +/*! + \since 4.5 + \fn QTextCharFormat::StyleStrategy QTextCharFormat::fontStyleStrategy() const + + Returns the current font style strategy. + + \sa setFontStyleStrategy() + \sa font() +*/ + + +/*! + \since 4.5 + \fn bool QTextCharFormat::fontKerning() const + Returns true if the font kerning is enabled. + + \sa setFontKerning() + \sa font() +*/ + + +/*! + \fn void QTextCharFormat::setFontFixedPitch(bool fixedPitch) + + If \a fixedPitch is true, sets the text format's font to be fixed pitch; + otherwise a non-fixed pitch font is used. + + \sa setFont() +*/ + + +/*! + \fn bool QTextCharFormat::fontFixedPitch() const + + Returns true if the text format's font is fixed pitch; otherwise + returns false. + + \sa font() +*/ + +/*! + \since 4.8 + + \fn void QTextCharFormat::setFontHintingPreference(QFont::HintingPreference hintingPreference) + + Sets the hinting preference of the text format's font to be \a hintingPreference. + + \sa setFont(), QFont::setHintingPreference() +*/ + +/*! + \since 4.8 + + \fn QFont::HintingPreference QTextCharFormat::fontHintingPreference() const + + Returns the hinting preference set for this text format. + + \sa font(), QFont::hintingPreference() +*/ + +/*! + \fn QPen QTextCharFormat::textOutline() const + + Returns the pen used to draw the outlines of characters in this format. +*/ + + +/*! + \fn void QTextCharFormat::setTextOutline(const QPen &pen) + + Sets the pen used to draw the outlines of characters to the given \a pen. +*/ + +/*! + \fn void QTextCharFormat::setToolTip(const QString &text) + \since 4.3 + + Sets the tool tip for a fragment of text to the given \a text. +*/ + +/*! + \fn QString QTextCharFormat::toolTip() const + \since 4.3 + + Returns the tool tip that is displayed for a fragment of text. +*/ + +/*! + \fn void QTextFormat::setForeground(const QBrush &brush) + + Sets the foreground brush to the specified \a brush. The foreground + brush is mostly used to render text. + + \sa foreground() clearForeground() setBackground() +*/ + + +/*! + \fn QBrush QTextFormat::foreground() const + + Returns the brush used to render foreground details, such as text, + frame outlines, and table borders. + + \sa setForeground() clearForeground() background() +*/ + +/*! + \fn void QTextFormat::clearForeground() + + Clears the brush used to paint the document's foreground. The default + brush will be used. + + \sa foreground() setForeground() clearBackground() +*/ + + +/*! + \fn void QTextCharFormat::setAnchor(bool anchor) + + If \a anchor is true, text with this format represents an anchor, and is + formatted in the appropriate way; otherwise the text is formatted normally. + (Anchors are hyperlinks which are often shown underlined and in a different + color from plain text.) + + The way the text is rendered is independent of whether or not the format + has a valid anchor defined. Use setAnchorHref(), and optionally + setAnchorNames() to create a hypertext link. + + \sa isAnchor() +*/ + + +/*! + \fn bool QTextCharFormat::isAnchor() const + + Returns true if the text is formatted as an anchor; otherwise + returns false. + + \sa setAnchor() setAnchorHref() setAnchorNames() +*/ + + +/*! + \fn void QTextCharFormat::setAnchorHref(const QString &value) + + Sets the hypertext link for the text format to the given \a value. + This is typically a URL like "http://example.com/index.html". + + The anchor will be displayed with the \a value as its display text; + if you want to display different text call setAnchorNames(). + + To format the text as a hypertext link use setAnchor(). +*/ + + +/*! + \fn QString QTextCharFormat::anchorHref() const + + Returns the text format's hypertext link, or an empty string if + none has been set. +*/ + + +/*! + \fn void QTextCharFormat::setAnchorName(const QString &name) + \obsolete + + This function is deprecated. Use setAnchorNames() instead. + + Sets the text format's anchor \a name. For the anchor to work as a + hyperlink, the destination must be set with setAnchorHref() and + the anchor must be enabled with setAnchor(). +*/ + +/*! + \fn void QTextCharFormat::setAnchorNames(const QStringList &names) + \since 4.3 + + Sets the text format's anchor \a names. For the anchor to work as a + hyperlink, the destination must be set with setAnchorHref() and + the anchor must be enabled with setAnchor(). +*/ + +/*! + \fn QString QTextCharFormat::anchorName() const + \obsolete + + This function is deprecated. Use anchorNames() instead. + + Returns the anchor name associated with this text format, or an empty + string if none has been set. If the anchor name is set, text with this + format can be the destination of a hypertext link. +*/ +QString QTextCharFormat::anchorName() const +{ + QVariant prop = property(AnchorName); + if (prop.userType() == QVariant::StringList) + return prop.toStringList().value(0); + else if (prop.userType() != QVariant::String) + return QString(); + return prop.toString(); +} + +/*! + \fn QStringList QTextCharFormat::anchorNames() const + \since 4.3 + + Returns the anchor names associated with this text format, or an empty + string list if none has been set. If the anchor names are set, text with this + format can be the destination of a hypertext link. +*/ +QStringList QTextCharFormat::anchorNames() const +{ + QVariant prop = property(AnchorName); + if (prop.userType() == QVariant::StringList) + return prop.toStringList(); + else if (prop.userType() != QVariant::String) + return QStringList(); + return QStringList(prop.toString()); +} + + +/*! + \fn void QTextCharFormat::setTableCellRowSpan(int tableCellRowSpan) + \internal + + If this character format is applied to characters in a table cell, + the cell will span \a tableCellRowSpan rows. +*/ + + +/*! + \fn int QTextCharFormat::tableCellRowSpan() const + \internal + + If this character format is applied to characters in a table cell, + this function returns the number of rows spanned by the text (this may + be 1); otherwise it returns 1. +*/ + +/*! + \fn void QTextCharFormat::setTableCellColumnSpan(int tableCellColumnSpan) + \internal + + If this character format is applied to characters in a table cell, + the cell will span \a tableCellColumnSpan columns. +*/ + + +/*! + \fn int QTextCharFormat::tableCellColumnSpan() const + \internal + + If this character format is applied to characters in a table cell, + this function returns the number of columns spanned by the text (this + may be 1); otherwise it returns 1. +*/ + +/*! + \fn void QTextCharFormat::setUnderlineColor(const QColor &color) + + Sets the underline color used for the characters with this format to + the \a color specified. + + \sa underlineColor() +*/ + +/*! + \fn QColor QTextCharFormat::underlineColor() const + + Returns the color used to underline the characters with this format. + + \sa setUnderlineColor() +*/ + +/*! + \fn void QTextCharFormat::setVerticalAlignment(VerticalAlignment alignment) + + Sets the vertical alignment used for the characters with this format to + the \a alignment specified. + + \sa verticalAlignment() +*/ + +/*! + \fn VerticalAlignment QTextCharFormat::verticalAlignment() const + + Returns the vertical alignment used for characters with this format. + + \sa setVerticalAlignment() +*/ + +/*! + Sets the text format's \a font. +*/ +void QTextCharFormat::setFont(const QFont &font) +{ + setFontFamily(font.family()); + + const qreal pointSize = font.pointSizeF(); + if (pointSize > 0) { + setFontPointSize(pointSize); + } else { + const int pixelSize = font.pixelSize(); + if (pixelSize > 0) + setProperty(QTextFormat::FontPixelSize, pixelSize); + } + + setFontWeight(font.weight()); + setFontItalic(font.italic()); + setUnderlineStyle(font.underline() ? SingleUnderline : NoUnderline); + setFontOverline(font.overline()); + setFontStrikeOut(font.strikeOut()); + setFontFixedPitch(font.fixedPitch()); + setFontCapitalization(font.capitalization()); + setFontWordSpacing(font.wordSpacing()); + if (font.letterSpacingType() == QFont::PercentageSpacing) + setFontLetterSpacing(font.letterSpacing()); + setFontStyleHint(font.styleHint()); + setFontStyleStrategy(font.styleStrategy()); + setFontKerning(font.kerning()); +} + +/*! + Returns the font for this character format. +*/ +QFont QTextCharFormat::font() const +{ + return d ? d->font() : QFont(); +} + +/*! + \class QTextBlockFormat + \reentrant + + \brief The QTextBlockFormat class provides formatting information for + blocks of text in a QTextDocument. + + \ingroup richtext-processing + + A document is composed of a list of blocks, represented by QTextBlock + objects. Each block can contain an item of some kind, such as a + paragraph of text, a table, a list, or an image. Every block has an + associated QTextBlockFormat that specifies its characteristics. + + To cater for left-to-right and right-to-left languages you can set + a block's direction with setDirection(). Paragraph alignment is + set with setAlignment(). Margins are controlled by setTopMargin(), + setBottomMargin(), setLeftMargin(), setRightMargin(). Overall + indentation is set with setIndent(), the indentation of the first + line with setTextIndent(). + + Line spacing is set with setLineHeight() and retrieved via lineHeight() + and lineHeightType(). The types of line spacing available are in the + LineHeightTypes enum. + + Line breaking can be enabled and disabled with setNonBreakableLines(). + + The brush used to paint the paragraph's background + is set with \l{QTextFormat::setBackground()}{setBackground()}, and other + aspects of the text's appearance can be customized by using the + \l{QTextFormat::setProperty()}{setProperty()} function with the + \c OutlinePen, \c ForegroundBrush, and \c BackgroundBrush + \l{QTextFormat::Property} values. + + If a text block is part of a list, it can also have a list format that + is accessible with the listFormat() function. + + \sa QTextBlock, QTextCharFormat +*/ + +/*! + \since 4.8 + \enum QTextBlockFormat::LineHeightTypes + + This enum describes the various types of line spacing support paragraphs can have. + + \value SingleHeight This is the default line height: single spacing. + \value ProportionalHeight This sets the spacing proportional to the line (in percentage). + For example, set to 200 for double spacing. + \value FixedHeight This sets the line height to a fixed line height (in pixels). + \value MinimumHeight This sets the minimum line height (in pixels). + \value LineDistanceHeight This adds the specified height between lines (in pixels). + + \sa lineHeight(), lineHeightType(), setLineHeight() +*/ + +/*! + \fn QTextBlockFormat::QTextBlockFormat() + + Constructs a new QTextBlockFormat. +*/ +QTextBlockFormat::QTextBlockFormat() : QTextFormat(BlockFormat) {} + +/*! + \internal + \fn QTextBlockFormat::QTextBlockFormat(const QTextFormat &other) + + Creates a new block format with the same attributes as the \a given + text format. +*/ +QTextBlockFormat::QTextBlockFormat(const QTextFormat &fmt) + : QTextFormat(fmt) +{ +} + +/*! + \since 4.4 + Sets the tab positions for the text block to those specified by + \a tabs. + + \sa tabPositions() +*/ +void QTextBlockFormat::setTabPositions(const QList &tabs) +{ + QList list; + QList::ConstIterator iter = tabs.constBegin(); + while (iter != tabs.constEnd()) { + QVariant v; + v.setValue(*iter); + list.append(v); + ++iter; + } + setProperty(TabPositions, list); +} + +/*! + \since 4.4 + Returns a list of tab positions defined for the text block. + + \sa setTabPositions() +*/ +QList QTextBlockFormat::tabPositions() const +{ + QVariant variant = property(TabPositions); + if(variant.isNull()) + return QList(); + QList answer; + QList variantsList = qvariant_cast >(variant); + QList::Iterator iter = variantsList.begin(); + while(iter != variantsList.end()) { + answer.append( qvariant_cast(*iter)); + ++iter; + } + return answer; +} + +/*! + \fn QTextBlockFormat::isValid() const + + Returns true if this block format is valid; otherwise returns + false. +*/ + +/*! + \fn void QTextFormat::setLayoutDirection(Qt::LayoutDirection direction) + + Sets the document's layout direction to the specified \a direction. + + \sa layoutDirection() +*/ + + +/*! + \fn Qt::LayoutDirection QTextFormat::layoutDirection() const + + Returns the document's layout direction. + + \sa setLayoutDirection() +*/ + + +/*! + \fn void QTextBlockFormat::setAlignment(Qt::Alignment alignment) + + Sets the paragraph's \a alignment. + + \sa alignment() +*/ + + +/*! + \fn Qt::Alignment QTextBlockFormat::alignment() const + + Returns the paragraph's alignment. + + \sa setAlignment() +*/ + + +/*! + \fn void QTextBlockFormat::setTopMargin(qreal margin) + + Sets the paragraph's top \a margin. + + \sa topMargin() setBottomMargin() setLeftMargin() setRightMargin() +*/ + + +/*! + \fn qreal QTextBlockFormat::topMargin() const + + Returns the paragraph's top margin. + + \sa setTopMargin() bottomMargin() +*/ + + +/*! + \fn void QTextBlockFormat::setBottomMargin(qreal margin) + + Sets the paragraph's bottom \a margin. + + \sa bottomMargin() setTopMargin() setLeftMargin() setRightMargin() +*/ + + +/*! + \fn qreal QTextBlockFormat::bottomMargin() const + + Returns the paragraph's bottom margin. + + \sa setBottomMargin() topMargin() +*/ + + +/*! + \fn void QTextBlockFormat::setLeftMargin(qreal margin) + + Sets the paragraph's left \a margin. Indentation can be applied separately + with setIndent(). + + \sa leftMargin() setRightMargin() setTopMargin() setBottomMargin() +*/ + + +/*! + \fn qreal QTextBlockFormat::leftMargin() const + + Returns the paragraph's left margin. + + \sa setLeftMargin() rightMargin() indent() +*/ + + +/*! + \fn void QTextBlockFormat::setRightMargin(qreal margin) + + Sets the paragraph's right \a margin. + + \sa rightMargin() setLeftMargin() setTopMargin() setBottomMargin() +*/ + + +/*! + \fn qreal QTextBlockFormat::rightMargin() const + + Returns the paragraph's right margin. + + \sa setRightMargin() leftMargin() +*/ + + +/*! + \fn void QTextBlockFormat::setTextIndent(qreal indent) + + Sets the \a indent for the first line in the block. This allows the first + line of a paragraph to be indented differently to the other lines, + enhancing the readability of the text. + + \sa textIndent() setLeftMargin() setRightMargin() setTopMargin() setBottomMargin() +*/ + + +/*! + \fn qreal QTextBlockFormat::textIndent() const + + Returns the paragraph's text indent. + + \sa setTextIndent() +*/ + + +/*! + \fn void QTextBlockFormat::setIndent(int indentation) + + Sets the paragraph's \a indentation. Margins are set independently of + indentation with setLeftMargin() and setTextIndent(). + The \a indentation is an integer that is multiplied with the document-wide + standard indent, resulting in the actual indent of the paragraph. + + \sa indent() QTextDocument::indentWidth() +*/ + + +/*! + \fn int QTextBlockFormat::indent() const + + Returns the paragraph's indent. + + \sa setIndent() +*/ + + +/*! + \fn void QTextBlockFormat::setLineHeight(qreal height, int heightType) + \since 4.8 + + This sets the line height for the paragraph to the value in height + which is dependant on heightType, described by the LineHeightTypes enum. + + \sa LineHeightTypes, lineHeight(), lineHeightType() +*/ + + +/*! + \fn qreal QTextBlockFormat::lineHeight(qreal scriptLineHeight, qreal scaling) const + \since 4.8 + + This returns what the height of the lines in the paragraph will be depending + on the given height of the script line and the scaling. The value that is returned + is also dependant on the given LineHeightType of the paragraph as well as the LineHeight + setting that has been set for the paragraph. The scaling is needed for the heights + that include a fixed number of pixels, to scale them appropriately for printing. + + \sa LineHeightTypes, setLineHeight(), lineHeightType() +*/ + + +/*! + \fn qreal QTextBlockFormat::lineHeight() const + \since 4.8 + + This returns the LineHeight property for the paragraph. + + \sa LineHeightTypes, setLineHeight(), lineHeightType() +*/ + + +/*! + \fn qreal QTextBlockFormat::lineHeightType() const + \since 4.8 + + This returns the LineHeightType property of the paragraph. + + \sa LineHeightTypes, setLineHeight(), lineHeight() +*/ + + +/*! + \fn void QTextBlockFormat::setNonBreakableLines(bool b) + + If \a b is true, the lines in the paragraph are treated as + non-breakable; otherwise they are breakable. + + \sa nonBreakableLines() +*/ + + +/*! + \fn bool QTextBlockFormat::nonBreakableLines() const + + Returns true if the lines in the paragraph are non-breakable; + otherwise returns false. + + \sa setNonBreakableLines() +*/ + +/*! + \fn QTextFormat::PageBreakFlags QTextBlockFormat::pageBreakPolicy() const + \since 4.2 + + Returns the currently set page break policy for the paragraph. The default is + QTextFormat::PageBreak_Auto. + + \sa setPageBreakPolicy() +*/ + +/*! + \fn void QTextBlockFormat::setPageBreakPolicy(PageBreakFlags policy) + \since 4.2 + + Sets the page break policy for the paragraph to \a policy. + + \sa pageBreakPolicy() +*/ + +/*! + \class QTextListFormat + \reentrant + + \brief The QTextListFormat class provides formatting information for + lists in a QTextDocument. + + \ingroup richtext-processing + + A list is composed of one or more items, represented as text blocks. + The list's format specifies the appearance of items in the list. + In particular, it determines the indentation and the style of each item. + + The indentation of the items is an integer value that causes each item to + be offset from the left margin by a certain amount. This value is read with + indent() and set with setIndent(). + + The style used to decorate each item is set with setStyle() and can be read + with the style() function. The style controls the type of bullet points and + numbering scheme used for items in the list. Note that lists that use the + decimal numbering scheme begin counting at 1 rather than 0. + + \sa QTextList +*/ + +/*! + \enum QTextListFormat::Style + + This enum describes the symbols used to decorate list items: + + \value ListDisc a filled circle + \value ListCircle an empty circle + \value ListSquare a filled square + \value ListDecimal decimal values in ascending order + \value ListLowerAlpha lower case Latin characters in alphabetical order + \value ListUpperAlpha upper case Latin characters in alphabetical order + \value ListLowerRoman lower case roman numerals (supports up to 4999 items only) + \value ListUpperRoman upper case roman numerals (supports up to 4999 items only) + \omitvalue ListStyleUndefined +*/ + +/*! + \fn QTextListFormat::QTextListFormat() + + Constructs a new list format object. +*/ +QTextListFormat::QTextListFormat() + : QTextFormat(ListFormat) +{ + setIndent(1); +} + +/*! + \internal + \fn QTextListFormat::QTextListFormat(const QTextFormat &other) + + Creates a new list format with the same attributes as the \a given + text format. +*/ +QTextListFormat::QTextListFormat(const QTextFormat &fmt) + : QTextFormat(fmt) +{ +} + +/*! + \fn bool QTextListFormat::isValid() const + + Returns true if this list format is valid; otherwise + returns false. +*/ + +/*! + \fn void QTextListFormat::setStyle(Style style) + + Sets the list format's \a style. + + \sa style() Style +*/ + +/*! + \fn Style QTextListFormat::style() const + + Returns the list format's style. + + \sa setStyle() Style +*/ + + +/*! + \fn void QTextListFormat::setIndent(int indentation) + + Sets the list format's \a indentation. + The indentation is multiplied by the QTextDocument::indentWidth + property to get the effective indent in pixels. + + \sa indent() +*/ + + +/*! + \fn int QTextListFormat::indent() const + + Returns the list format's indentation. + The indentation is multiplied by the QTextDocument::indentWidth + property to get the effective indent in pixels. + + \sa setIndent() +*/ + +/*! + \fn void QTextListFormat::setNumberPrefix(const QString &numberPrefix) + \since 4.8 + + Sets the list format's number prefix. This can be used with all + sorted list types. It does not have any effect on unsorted list types. + + \sa numberPrefix() +*/ + +/*! + \fn int QTextListFormat::numberPrefix() const + \since 4.8 + + Returns the list format's number prefix. + + \sa setNumberPrefix() +*/ + +/*! + \fn void QTextListFormat::setNumberSuffix(const QString &numberSuffix) + \since 4.8 + + Sets the list format's number suffix. This can be used with all + sorted list types. It does not have any effect on unsorted list types. + The default suffix is ".". + + \sa numberSuffix() +*/ + +/*! + \fn int QTextListFormat::numberSuffix() const + \since 4.8 + + Returns the list format's number suffix. + + \sa setNumberSuffix() +*/ + +/*! + \class QTextFrameFormat + \reentrant + + \brief The QTextFrameFormat class provides formatting information for + frames in a QTextDocument. + + \ingroup richtext-processing + + A text frame groups together one or more blocks of text, providing a layer + of structure larger than the paragraph. The format of a frame specifies + how it is rendered and positioned on the screen. It does not directly + specify the behavior of the text formatting within, but provides + constraints on the layout of its children. + + The frame format defines the width() and height() of the frame on the + screen. Each frame can have a border() that surrounds its contents with + a rectangular box. The border is surrounded by a margin() around the frame, + and the contents of the frame are kept separate from the border by the + frame's padding(). This scheme is similar to the box model used by Cascading + Style Sheets for HTML pages. + + \img qtextframe-style.png + + The position() of a frame is set using setPosition() and determines how it + is located relative to the surrounding text. + + The validity of a QTextFrameFormat object can be determined with the + isValid() function. + + \sa QTextFrame QTextBlockFormat +*/ + +/*! + \enum QTextFrameFormat::Position + + This enum describes how a frame is located relative to the surrounding text. + + \value InFlow + \value FloatLeft + \value FloatRight + + \sa position() CssFloat +*/ + +/*! + \enum QTextFrameFormat::BorderStyle + \since 4.3 + + This enum describes different border styles for the text frame. + + \value BorderStyle_None + \value BorderStyle_Dotted + \value BorderStyle_Dashed + \value BorderStyle_Solid + \value BorderStyle_Double + \value BorderStyle_DotDash + \value BorderStyle_DotDotDash + \value BorderStyle_Groove + \value BorderStyle_Ridge + \value BorderStyle_Inset + \value BorderStyle_Outset + + \sa borderStyle() FrameBorderStyle +*/ + +/*! + \fn QTextFrameFormat::QTextFrameFormat() + + Constructs a text frame format object with the default properties. +*/ +QTextFrameFormat::QTextFrameFormat() : QTextFormat(FrameFormat) +{ + setBorderStyle(BorderStyle_Outset); + setBorderBrush(Qt::darkGray); +} + +/*! + \internal + \fn QTextFrameFormat::QTextFrameFormat(const QTextFormat &other) + + Creates a new frame format with the same attributes as the \a given + text format. +*/ +QTextFrameFormat::QTextFrameFormat(const QTextFormat &fmt) + : QTextFormat(fmt) +{ +} + +/*! + \fn QTextFrameFormat::isValid() const + + Returns true if the format description is valid; otherwise returns false. +*/ + +/*! + \fn QTextFrameFormat::setPosition(Position policy) + + Sets the \a policy for positioning frames with this frame format. + +*/ + +/*! + \fn Position QTextFrameFormat::position() const + + Returns the positioning policy for frames with this frame format. +*/ + +/*! + \fn QTextFrameFormat::setBorder(qreal width) + + Sets the \a width (in pixels) of the frame's border. +*/ + +/*! + \fn qreal QTextFrameFormat::border() const + + Returns the width of the border in pixels. +*/ + +/*! + \fn QTextFrameFormat::setBorderBrush(const QBrush &brush) + \since 4.3 + + Sets the \a brush used for the frame's border. +*/ + +/*! + \fn QBrush QTextFrameFormat::borderBrush() const + \since 4.3 + + Returns the brush used for the frame's border. +*/ + +/*! + \fn QTextFrameFormat::setBorderStyle(BorderStyle style) + \since 4.3 + + Sets the \a style of the frame's border. +*/ + +/*! + \fn BorderStyle QTextFrameFormat::borderStyle() const + \since 4.3 + + Returns the style of the frame's border. +*/ + +/*! + \fn QTextFrameFormat::setMargin(qreal margin) + + Sets the frame's \a margin in pixels. + This method also sets the left, right, top and bottom margins + of the frame to the same value. The individual margins override + the general margin. +*/ +void QTextFrameFormat::setMargin(qreal amargin) +{ + setProperty(FrameMargin, amargin); + setProperty(FrameTopMargin, amargin); + setProperty(FrameBottomMargin, amargin); + setProperty(FrameLeftMargin, amargin); + setProperty(FrameRightMargin, amargin); +} + + +/*! + \fn qreal QTextFrameFormat::margin() const + + Returns the width of the frame's external margin in pixels. +*/ + +/*! + \fn QTextFrameFormat::setTopMargin(qreal margin) + \since 4.3 + + Sets the frame's top \a margin in pixels. +*/ + +/*! + \fn qreal QTextFrameFormat::topMargin() const + \since 4.3 + + Returns the width of the frame's top margin in pixels. +*/ +qreal QTextFrameFormat::topMargin() const +{ + if (!hasProperty(FrameTopMargin)) + return margin(); + return doubleProperty(FrameTopMargin); +} + +/*! + \fn QTextFrameFormat::setBottomMargin(qreal margin) + \since 4.3 + + Sets the frame's bottom \a margin in pixels. +*/ + +/*! + \fn qreal QTextFrameFormat::bottomMargin() const + \since 4.3 + + Returns the width of the frame's bottom margin in pixels. +*/ +qreal QTextFrameFormat::bottomMargin() const +{ + if (!hasProperty(FrameBottomMargin)) + return margin(); + return doubleProperty(FrameBottomMargin); +} + +/*! + \fn QTextFrameFormat::setLeftMargin(qreal margin) + \since 4.3 + + Sets the frame's left \a margin in pixels. +*/ + +/*! + \fn qreal QTextFrameFormat::leftMargin() const + \since 4.3 + + Returns the width of the frame's left margin in pixels. +*/ +qreal QTextFrameFormat::leftMargin() const +{ + if (!hasProperty(FrameLeftMargin)) + return margin(); + return doubleProperty(FrameLeftMargin); +} + +/*! + \fn QTextFrameFormat::setRightMargin(qreal margin) + \since 4.3 + + Sets the frame's right \a margin in pixels. +*/ + +/*! + \fn qreal QTextFrameFormat::rightMargin() const + \since 4.3 + + Returns the width of the frame's right margin in pixels. +*/ +qreal QTextFrameFormat::rightMargin() const +{ + if (!hasProperty(FrameRightMargin)) + return margin(); + return doubleProperty(FrameRightMargin); +} + +/*! + \fn QTextFrameFormat::setPadding(qreal width) + + Sets the \a width of the frame's internal padding in pixels. +*/ + +/*! + \fn qreal QTextFrameFormat::padding() const + + Returns the width of the frame's internal padding in pixels. +*/ + +/*! + \fn QTextFrameFormat::setWidth(const QTextLength &width) + + Sets the frame's border rectangle's \a width. + + \sa QTextLength +*/ + +/*! + \fn QTextFrameFormat::setWidth(qreal width) + \overload + + Convenience method that sets the width of the frame's border + rectangle's width to the specified fixed \a width. +*/ + +/*! + \fn QTextFormat::PageBreakFlags QTextFrameFormat::pageBreakPolicy() const + \since 4.2 + + Returns the currently set page break policy for the frame/table. The default is + QTextFormat::PageBreak_Auto. + + \sa setPageBreakPolicy() +*/ + +/*! + \fn void QTextFrameFormat::setPageBreakPolicy(PageBreakFlags policy) + \since 4.2 + + Sets the page break policy for the frame/table to \a policy. + + \sa pageBreakPolicy() +*/ + +/*! + \fn QTextLength QTextFrameFormat::width() const + + Returns the width of the frame's border rectangle. + + \sa QTextLength +*/ + +/*! + \fn void QTextFrameFormat::setHeight(const QTextLength &height) + + Sets the frame's \a height. +*/ + +/*! + \fn void QTextFrameFormat::setHeight(qreal height) + \overload + + Sets the frame's \a height. +*/ + +/*! + \fn qreal QTextFrameFormat::height() const + + Returns the height of the frame's border rectangle. +*/ + +/*! + \class QTextTableFormat + \reentrant + + \brief The QTextTableFormat class provides formatting information for + tables in a QTextDocument. + + \ingroup richtext-processing + + A table is a group of cells ordered into rows and columns. Each table + contains at least one row and one column. Each cell contains a block. + Tables in rich text documents are formatted using the properties + defined in this class. + + Tables are horizontally justified within their parent frame according to the + table's alignment. This can be read with the alignment() function and set + with setAlignment(). + + Cells within the table are separated by cell spacing. The number of pixels + between cells is set with setCellSpacing() and read with cellSpacing(). + The contents of each cell is surrounded by cell padding. The number of pixels + between each cell edge and its contents is set with setCellPadding() and read + with cellPadding(). + + \image qtexttableformat-cell.png + + The table's background color can be read with the background() function, + and can be specified with setBackground(). The background color of each + cell can be set independently, and will control the color of the cell within + the padded area. + + The table format also provides a way to constrain the widths of the columns + in the table. Columns can be assigned a fixed width, a variable width, or + a percentage of the available width (see QTextLength). The columns() function + returns the number of columns with constraints, and the + columnWidthConstraints() function returns the constraints defined for the + table. These quantities can also be set by calling setColumnWidthConstraints() + with a vector containing new constraints. If no constraints are + required, clearColumnWidthConstraints() can be used to remove them. + + \sa QTextTable QTextTableCell QTextLength +*/ + +/*! + \fn QTextTableFormat::QTextTableFormat() + + Constructs a new table format object. +*/ +QTextTableFormat::QTextTableFormat() + : QTextFrameFormat() +{ + setObjectType(TableObject); + setCellSpacing(2); + setBorder(1); +} + +/*! + \internal + \fn QTextTableFormat::QTextTableFormat(const QTextFormat &other) + + Creates a new table format with the same attributes as the \a given + text format. +*/ +QTextTableFormat::QTextTableFormat(const QTextFormat &fmt) + : QTextFrameFormat(fmt) +{ +} + +/*! + \fn bool QTextTableFormat::isValid() const + + Returns true if this table format is valid; otherwise + returns false. +*/ + + +/*! + \fn int QTextTableFormat::columns() const + + Returns the number of columns specified by the table format. +*/ + + +/*! + \internal + \fn void QTextTableFormat::setColumns(int columns) + + Sets the number of \a columns required by the table format. + + \sa columns() +*/ + +/*! + \fn void QTextTableFormat::clearColumnWidthConstraints() + + Clears the column width constraints for the table. + + \sa columnWidthConstraints() setColumnWidthConstraints() +*/ + +/*! + \fn void QTextTableFormat::setColumnWidthConstraints(const QVector &constraints) + + Sets the column width \a constraints for the table. + + \sa columnWidthConstraints() clearColumnWidthConstraints() +*/ + +/*! + \fn QVector QTextTableFormat::columnWidthConstraints() const + + Returns a list of constraints used by this table format to control the + appearance of columns in a table. + + \sa setColumnWidthConstraints() +*/ + +/*! + \fn qreal QTextTableFormat::cellSpacing() const + + Returns the table's cell spacing. This describes the distance between + adjacent cells. +*/ + +/*! + \fn void QTextTableFormat::setCellSpacing(qreal spacing) + + Sets the cell \a spacing for the table. This determines the distance + between adjacent cells. +*/ + +/*! + \fn qreal QTextTableFormat::cellPadding() const + + Returns the table's cell padding. This describes the distance between + the border of a cell and its contents. +*/ + +/*! + \fn void QTextTableFormat::setCellPadding(qreal padding) + + Sets the cell \a padding for the table. This determines the distance + between the border of a cell and its contents. +*/ + +/*! + \fn void QTextTableFormat::setAlignment(Qt::Alignment alignment) + + Sets the table's \a alignment. + + \sa alignment() +*/ + +/*! + \fn Qt::Alignment QTextTableFormat::alignment() const + + Returns the table's alignment. + + \sa setAlignment() +*/ + +/*! + \fn void QTextTableFormat::setHeaderRowCount(int count) + \since 4.2 + + Declares the first \a count rows of the table as table header. + The table header rows get repeated when a table is broken + across a page boundary. +*/ + +/*! + \fn int QTextTableFormat::headerRowCount() const + \since 4.2 + + Returns the number of rows in the table that define the header. + + \sa setHeaderRowCount() +*/ + +/*! + \fn void QTextFormat::setBackground(const QBrush &brush) + + Sets the brush use to paint the document's background to the + \a brush specified. + + \sa background() clearBackground() setForeground() +*/ + +/*! + \fn QColor QTextFormat::background() const + + Returns the brush used to paint the document's background. + + \sa setBackground() clearBackground() foreground() +*/ + +/*! + \fn void QTextFormat::clearBackground() + + Clears the brush used to paint the document's background. The default + brush will be used. + + \sa background() setBackground() clearForeground() +*/ + + +/*! + \class QTextImageFormat + \reentrant + + \brief The QTextImageFormat class provides formatting information for + images in a QTextDocument. + + \ingroup richtext-processing + + Inline images are represented by an object replacement character + (0xFFFC in Unicode) which has an associated QTextImageFormat. The + image format specifies a name with setName() that is used to + locate the image. The size of the rectangle that the image will + occupy is specified using setWidth() and setHeight(). + + Images can be supplied in any format for which Qt has an image + reader, so SVG drawings can be included alongside PNG, TIFF and + other bitmap formats. + + \sa QImage, QImageReader +*/ + +/*! + \fn QTextImageFormat::QTextImageFormat() + + Creates a new image format object. +*/ +QTextImageFormat::QTextImageFormat() : QTextCharFormat() { setObjectType(ImageObject); } + +/*! + \internal + \fn QTextImageFormat::QTextImageFormat(const QTextFormat &other) + + Creates a new image format with the same attributes as the \a given + text format. +*/ +QTextImageFormat::QTextImageFormat(const QTextFormat &fmt) + : QTextCharFormat(fmt) +{ +} + +/*! + \fn bool QTextImageFormat::isValid() const + + Returns true if this image format is valid; otherwise returns false. +*/ + + +/*! + \fn void QTextImageFormat::setName(const QString &name) + + Sets the \a name of the image. The \a name is used to locate the image + in the application's resources. + + \sa name() +*/ + + +/*! + \fn QString QTextImageFormat::name() const + + Returns the name of the image. The name refers to an entry in the + application's resources file. + + \sa setName() +*/ + +/*! + \fn void QTextImageFormat::setWidth(qreal width) + + Sets the \a width of the rectangle occupied by the image. + + \sa width() setHeight() +*/ + + +// ### Qt5 qreal replace with a QTextLength +/*! + \fn qreal QTextImageFormat::width() const + + Returns the width of the rectangle occupied by the image. + + \sa height() setWidth() +*/ + + +/*! + \fn void QTextImageFormat::setHeight(qreal height) + + Sets the \a height of the rectangle occupied by the image. + + \sa height() setWidth() +*/ + + +// ### Qt5 qreal replace with a QTextLength +/*! + \fn qreal QTextImageFormat::height() const + + Returns the height of the rectangle occupied by the image. + + \sa width() setHeight() +*/ + +/*! + \fn void QTextCharFormat::setFontCapitalization(QFont::Capitalization capitalization) + \since 4.4 + + Sets the capitalization of the text that apppears in this font to \a capitalization. + + A font's capitalization makes the text appear in the selected capitalization mode. + + \sa fontCapitalization() +*/ + +/*! + \fn Capitalization QTextCharFormat::fontCapitalization() const + \since 4.4 + + Returns the current capitalization type of the font. +*/ + +/*! + \fn void QTextCharFormat::setFontLetterSpacing(qreal spacing) + \since 4.4 + + Sets the letter spacing of this format to the given \a spacing, in percent. + A value of 100 indicates default spacing; a value of 200 doubles the amount + of space a letter takes. + + \sa fontLetterSpacing() +*/ + +/*! + \fn qreal QTextCharFormat::fontLetterSpacing() const + \since 4.4 + + Returns the current letter spacing percentage. +*/ + +/*! + \fn void QTextCharFormat::setFontWordSpacing(qreal spacing) + \since 4.4 + + Sets the word spacing of this format to the given \a spacing, in pixels. + + \sa fontWordSpacing() +*/ + +/*! + \fn qreal QTextCharFormat::fontWordSpacing() const + \since 4.4 + + Returns the current word spacing value. +*/ + +/*! + \fn qreal QTextTableCellFormat::topPadding() const + \since 4.4 + + Gets the top padding of the table cell. + + \sa setTopPadding(), leftPadding(), rightPadding(), bottomPadding() +*/ + +/*! + \fn qreal QTextTableCellFormat::bottomPadding() const + \since 4.4 + + Gets the bottom padding of the table cell. + + \sa setBottomPadding(), leftPadding(), rightPadding(), topPadding() +*/ + +/*! + \fn qreal QTextTableCellFormat::leftPadding() const + \since 4.4 + + Gets the left padding of the table cell. + + \sa setLeftPadding(), rightPadding(), topPadding(), bottomPadding() +*/ + +/*! + \fn qreal QTextTableCellFormat::rightPadding() const + \since 4.4 + + Gets the right padding of the table cell. + + \sa setRightPadding(), leftPadding(), topPadding(), bottomPadding() +*/ + +/*! + \fn void QTextTableCellFormat::setTopPadding(qreal padding) + \since 4.4 + + Sets the top \a padding of the table cell. + + \sa topPadding(), setLeftPadding(), setRightPadding(), setBottomPadding() +*/ + +/*! + \fn void QTextTableCellFormat::setBottomPadding(qreal padding) + \since 4.4 + + Sets the bottom \a padding of the table cell. + + \sa bottomPadding(), setLeftPadding(), setRightPadding(), setTopPadding() +*/ + +/*! + \fn void QTextTableCellFormat::setLeftPadding(qreal padding) + \since 4.4 + + Sets the left \a padding of the table cell. + + \sa leftPadding(), setRightPadding(), setTopPadding(), setBottomPadding() +*/ + +/*! + \fn void QTextTableCellFormat::setRightPadding(qreal padding) + \since 4.4 + + Sets the right \a padding of the table cell. + + \sa rightPadding(), setLeftPadding(), setTopPadding(), setBottomPadding() +*/ + +/*! + \fn void QTextTableCellFormat::setPadding(qreal padding) + \since 4.4 + + Sets the left, right, top, and bottom \a padding of the table cell. + + \sa setLeftPadding(), setRightPadding(), setTopPadding(), setBottomPadding() +*/ + +/*! + \fn bool QTextTableCellFormat::isValid() const + \since 4.4 + + Returns true if this table cell format is valid; otherwise returns false. +*/ + +/*! + \fn QTextTableCellFormat::QTextTableCellFormat() + \since 4.4 + + Constructs a new table cell format object. +*/ +QTextTableCellFormat::QTextTableCellFormat() + : QTextCharFormat() +{ + setObjectType(TableCellObject); +} + +/*! + \internal + \fn QTextTableCellFormat::QTextTableCellFormat(const QTextFormat &other) + + Creates a new table cell format with the same attributes as the \a given + text format. +*/ +QTextTableCellFormat::QTextTableCellFormat(const QTextFormat &fmt) + : QTextCharFormat(fmt) +{ +} + +/*! + \class QTextTableCellFormat + \reentrant + \since 4.4 + + \brief The QTextTableCellFormat class provides formatting information for + table cells in a QTextDocument. + + \ingroup richtext-processing + + The table cell format of a table cell in a document specifies the visual + properties of the table cell. + + The padding properties of a table cell are controlled by setLeftPadding(), + setRightPadding(), setTopPadding(), and setBottomPadding(). All the paddings + can be set at once using setPadding(). + + \sa QTextFormat QTextBlockFormat QTextTableFormat QTextCharFormat +*/ + +// ------------------------------------------------------ + + +QTextFormatCollection::QTextFormatCollection(const QTextFormatCollection &rhs) +{ + formats = rhs.formats; + objFormats = rhs.objFormats; +} + +QTextFormatCollection &QTextFormatCollection::operator=(const QTextFormatCollection &rhs) +{ + formats = rhs.formats; + objFormats = rhs.objFormats; + return *this; +} + +QTextFormatCollection::~QTextFormatCollection() +{ +} + +int QTextFormatCollection::indexForFormat(const QTextFormat &format) +{ + uint hash = getHash(format.d, format.format_type); + QMultiHash::const_iterator i = hashes.find(hash); + while (i != hashes.end() && i.key() == hash) { + if (formats.value(i.value()) == format) { + return i.value(); + } + ++i; + } + + int idx = formats.size(); + formats.append(format); + + QT_TRY{ + QTextFormat &f = formats.last(); + if (!f.d) + f.d = new QTextFormatPrivate; + f.d->resolveFont(defaultFnt); + + if (!hashes.contains(hash, idx)) + hashes.insert(hash, idx); + + } QT_CATCH(...) { + formats.pop_back(); + QT_RETHROW; + } + return idx; +} + +bool QTextFormatCollection::hasFormatCached(const QTextFormat &format) const +{ + uint hash = getHash(format.d, format.format_type); + QMultiHash::const_iterator i = hashes.find(hash); + while (i != hashes.end() && i.key() == hash) { + if (formats.value(i.value()) == format) { + return true; + } + ++i; + } + return false; +} + +QTextFormat QTextFormatCollection::objectFormat(int objectIndex) const +{ + if (objectIndex == -1) + return QTextFormat(); + return format(objFormats.at(objectIndex)); +} + +void QTextFormatCollection::setObjectFormat(int objectIndex, const QTextFormat &f) +{ + const int formatIndex = indexForFormat(f); + objFormats[objectIndex] = formatIndex; +} + +int QTextFormatCollection::objectFormatIndex(int objectIndex) const +{ + if (objectIndex == -1) + return -1; + return objFormats.at(objectIndex); +} + +void QTextFormatCollection::setObjectFormatIndex(int objectIndex, int formatIndex) +{ + objFormats[objectIndex] = formatIndex; +} + +int QTextFormatCollection::createObjectIndex(const QTextFormat &f) +{ + const int objectIndex = objFormats.size(); + objFormats.append(indexForFormat(f)); + return objectIndex; +} + +QTextFormat QTextFormatCollection::format(int idx) const +{ + if (idx < 0 || idx >= formats.count()) + return QTextFormat(); + + return formats.at(idx); +} + +void QTextFormatCollection::setDefaultFont(const QFont &f) +{ + defaultFnt = f; + for (int i = 0; i < formats.count(); ++i) + if (formats[i].d) + formats[i].d->resolveFont(defaultFnt); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qtextformat.h b/src/gui/text/qtextformat.h new file mode 100644 index 0000000000..ff28eaa1b2 --- /dev/null +++ b/src/gui/text/qtextformat.h @@ -0,0 +1,976 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTEXTFORMAT_H +#define QTEXTFORMAT_H + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QString; +class QVariant; +class QFont; + +class QTextFormatCollection; +class QTextFormatPrivate; +class QTextBlockFormat; +class QTextCharFormat; +class QTextListFormat; +class QTextTableFormat; +class QTextFrameFormat; +class QTextImageFormat; +class QTextTableCellFormat; +class QTextFormat; +class QTextObject; +class QTextCursor; +class QTextDocument; +class QTextLength; + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QTextLength &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QTextLength &); +#endif + +class Q_GUI_EXPORT QTextLength +{ +public: + enum Type { VariableLength = 0, FixedLength, PercentageLength }; + + inline QTextLength() : lengthType(VariableLength), fixedValueOrPercentage(0) {} + + inline explicit QTextLength(Type type, qreal value); + + inline Type type() const { return lengthType; } + inline qreal value(qreal maximumLength) const + { + switch (lengthType) { + case FixedLength: return fixedValueOrPercentage; + case VariableLength: return maximumLength; + case PercentageLength: return fixedValueOrPercentage * maximumLength / qreal(100); + } + return -1; + } + + inline qreal rawValue() const { return fixedValueOrPercentage; } + + inline bool operator==(const QTextLength &other) const + { return lengthType == other.lengthType + && qFuzzyCompare(fixedValueOrPercentage, other.fixedValueOrPercentage); } + inline bool operator!=(const QTextLength &other) const + { return lengthType != other.lengthType + || !qFuzzyCompare(fixedValueOrPercentage, other.fixedValueOrPercentage); } + operator QVariant() const; + +private: + Type lengthType; + qreal fixedValueOrPercentage; + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QTextLength &); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QTextLength &); +}; + +inline QTextLength::QTextLength(Type atype, qreal avalue) + : lengthType(atype), fixedValueOrPercentage(avalue) {} + +#ifndef QT_NO_DATASTREAM +Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QTextFormat &); +Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QTextFormat &); +#endif + +class Q_GUI_EXPORT QTextFormat +{ + Q_GADGET + Q_ENUMS(FormatType Property ObjectTypes) +public: + enum FormatType { + InvalidFormat = -1, + BlockFormat = 1, + CharFormat = 2, + ListFormat = 3, + TableFormat = 4, + FrameFormat = 5, + + UserFormat = 100 + }; + + enum Property { + ObjectIndex = 0x0, + + // paragraph and char + CssFloat = 0x0800, + LayoutDirection = 0x0801, + + OutlinePen = 0x810, + BackgroundBrush = 0x820, + ForegroundBrush = 0x821, + // Internal to qtextlayout.cpp: ObjectSelectionBrush = 0x822 + BackgroundImageUrl = 0x823, + + // paragraph + BlockAlignment = 0x1010, + BlockTopMargin = 0x1030, + BlockBottomMargin = 0x1031, + BlockLeftMargin = 0x1032, + BlockRightMargin = 0x1033, + TextIndent = 0x1034, + TabPositions = 0x1035, + BlockIndent = 0x1040, + LineHeight = 0x1048, + LineHeightType = 0x1049, + BlockNonBreakableLines = 0x1050, + BlockTrailingHorizontalRulerWidth = 0x1060, + + // character properties + FirstFontProperty = 0x1FE0, + FontCapitalization = FirstFontProperty, + FontLetterSpacing = 0x1FE1, + FontWordSpacing = 0x1FE2, + FontStyleHint = 0x1FE3, + FontStyleStrategy = 0x1FE4, + FontKerning = 0x1FE5, + FontHintingPreference = 0x1FE6, + FontFamily = 0x2000, + FontPointSize = 0x2001, + FontSizeAdjustment = 0x2002, + FontSizeIncrement = FontSizeAdjustment, // old name, compat + FontWeight = 0x2003, + FontItalic = 0x2004, + FontUnderline = 0x2005, // deprecated, use TextUnderlineStyle instead + FontOverline = 0x2006, + FontStrikeOut = 0x2007, + FontFixedPitch = 0x2008, + FontPixelSize = 0x2009, + LastFontProperty = FontPixelSize, + + TextUnderlineColor = 0x2010, + TextVerticalAlignment = 0x2021, + TextOutline = 0x2022, + TextUnderlineStyle = 0x2023, + TextToolTip = 0x2024, + + IsAnchor = 0x2030, + AnchorHref = 0x2031, + AnchorName = 0x2032, + ObjectType = 0x2f00, + + // list properties + ListStyle = 0x3000, + ListIndent = 0x3001, + ListNumberPrefix = 0x3002, + ListNumberSuffix = 0x3003, + + // table and frame properties + FrameBorder = 0x4000, + FrameMargin = 0x4001, + FramePadding = 0x4002, + FrameWidth = 0x4003, + FrameHeight = 0x4004, + FrameTopMargin = 0x4005, + FrameBottomMargin = 0x4006, + FrameLeftMargin = 0x4007, + FrameRightMargin = 0x4008, + FrameBorderBrush = 0x4009, + FrameBorderStyle = 0x4010, + + TableColumns = 0x4100, + TableColumnWidthConstraints = 0x4101, + TableCellSpacing = 0x4102, + TableCellPadding = 0x4103, + TableHeaderRowCount = 0x4104, + + // table cell properties + TableCellRowSpan = 0x4810, + TableCellColumnSpan = 0x4811, + + TableCellTopPadding = 0x4812, + TableCellBottomPadding = 0x4813, + TableCellLeftPadding = 0x4814, + TableCellRightPadding = 0x4815, + + // image properties + ImageName = 0x5000, + ImageWidth = 0x5010, + ImageHeight = 0x5011, + + // internal + /* + SuppressText = 0x5012, + SuppressBackground = 0x513 + */ + + // selection properties + FullWidthSelection = 0x06000, + + // page break properties + PageBreakPolicy = 0x7000, + + // -- + UserProperty = 0x100000 + }; + + enum ObjectTypes { + NoObject, + ImageObject, + TableObject, + TableCellObject, + + UserObject = 0x1000 + }; + + enum PageBreakFlag { + PageBreak_Auto = 0, + PageBreak_AlwaysBefore = 0x001, + PageBreak_AlwaysAfter = 0x010 + // PageBreak_AlwaysInside = 0x100 + }; + Q_DECLARE_FLAGS(PageBreakFlags, PageBreakFlag) + + QTextFormat(); + + explicit QTextFormat(int type); + + QTextFormat(const QTextFormat &rhs); + QTextFormat &operator=(const QTextFormat &rhs); + ~QTextFormat(); + + void merge(const QTextFormat &other); + + inline bool isValid() const { return type() != InvalidFormat; } + + int type() const; + + int objectIndex() const; + void setObjectIndex(int object); + + QVariant property(int propertyId) const; + void setProperty(int propertyId, const QVariant &value); + void clearProperty(int propertyId); + bool hasProperty(int propertyId) const; + + bool boolProperty(int propertyId) const; + int intProperty(int propertyId) const; + qreal doubleProperty(int propertyId) const; + QString stringProperty(int propertyId) const; + QColor colorProperty(int propertyId) const; + QPen penProperty(int propertyId) const; + QBrush brushProperty(int propertyId) const; + QTextLength lengthProperty(int propertyId) const; + QVector lengthVectorProperty(int propertyId) const; + + void setProperty(int propertyId, const QVector &lengths); + + QMap properties() const; + int propertyCount() const; + + inline void setObjectType(int type); + inline int objectType() const + { return intProperty(ObjectType); } + + inline bool isCharFormat() const { return type() == CharFormat; } + inline bool isBlockFormat() const { return type() == BlockFormat; } + inline bool isListFormat() const { return type() == ListFormat; } + inline bool isFrameFormat() const { return type() == FrameFormat; } + inline bool isImageFormat() const { return type() == CharFormat && objectType() == ImageObject; } + inline bool isTableFormat() const { return type() == FrameFormat && objectType() == TableObject; } + inline bool isTableCellFormat() const { return type() == CharFormat && objectType() == TableCellObject; } + + QTextBlockFormat toBlockFormat() const; + QTextCharFormat toCharFormat() const; + QTextListFormat toListFormat() const; + QTextTableFormat toTableFormat() const; + QTextFrameFormat toFrameFormat() const; + QTextImageFormat toImageFormat() const; + QTextTableCellFormat toTableCellFormat() const; + + bool operator==(const QTextFormat &rhs) const; + inline bool operator!=(const QTextFormat &rhs) const { return !operator==(rhs); } + operator QVariant() const; + + inline void setLayoutDirection(Qt::LayoutDirection direction) + { setProperty(QTextFormat::LayoutDirection, direction); } + inline Qt::LayoutDirection layoutDirection() const + { return Qt::LayoutDirection(intProperty(QTextFormat::LayoutDirection)); } + + inline void setBackground(const QBrush &brush) + { setProperty(BackgroundBrush, brush); } + inline QBrush background() const + { return brushProperty(BackgroundBrush); } + inline void clearBackground() + { clearProperty(BackgroundBrush); } + + inline void setForeground(const QBrush &brush) + { setProperty(ForegroundBrush, brush); } + inline QBrush foreground() const + { return brushProperty(ForegroundBrush); } + inline void clearForeground() + { clearProperty(ForegroundBrush); } + +private: + QSharedDataPointer d; + qint32 format_type; + + friend class QTextFormatCollection; + friend class QTextCharFormat; + friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QTextFormat &); + friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QTextFormat &); +}; + +inline void QTextFormat::setObjectType(int atype) +{ setProperty(ObjectType, atype); } + +Q_DECLARE_OPERATORS_FOR_FLAGS(QTextFormat::PageBreakFlags) + +class Q_GUI_EXPORT QTextCharFormat : public QTextFormat +{ +public: + enum VerticalAlignment { + AlignNormal = 0, + AlignSuperScript, + AlignSubScript, + AlignMiddle, + AlignTop, + AlignBottom + }; + enum UnderlineStyle { // keep in sync with Qt::PenStyle! + NoUnderline, + SingleUnderline, + DashUnderline, + DotLine, + DashDotLine, + DashDotDotLine, + WaveUnderline, + SpellCheckUnderline + }; + + QTextCharFormat(); + + bool isValid() const { return isCharFormat(); } + void setFont(const QFont &font); + QFont font() const; + + inline void setFontFamily(const QString &family) + { setProperty(FontFamily, family); } + inline QString fontFamily() const + { return stringProperty(FontFamily); } + + inline void setFontPointSize(qreal size) + { setProperty(FontPointSize, size); } + inline qreal fontPointSize() const + { return doubleProperty(FontPointSize); } + + inline void setFontWeight(int weight) + { if (weight == QFont::Normal) weight = 0; setProperty(FontWeight, weight); } + inline int fontWeight() const + { int weight = intProperty(FontWeight); if (weight == 0) weight = QFont::Normal; return weight; } + inline void setFontItalic(bool italic) + { setProperty(FontItalic, italic); } + inline bool fontItalic() const + { return boolProperty(FontItalic); } + inline void setFontCapitalization(QFont::Capitalization capitalization) + { setProperty(FontCapitalization, capitalization); } + inline QFont::Capitalization fontCapitalization() const + { return static_cast(intProperty(FontCapitalization)); } + inline void setFontLetterSpacing(qreal spacing) + { setProperty(FontLetterSpacing, spacing); } + inline qreal fontLetterSpacing() const + { return doubleProperty(FontLetterSpacing); } + inline void setFontWordSpacing(qreal spacing) + { setProperty(FontWordSpacing, spacing); } + inline qreal fontWordSpacing() const + { return doubleProperty(FontWordSpacing); } + + inline void setFontUnderline(bool underline) + { setProperty(TextUnderlineStyle, underline ? SingleUnderline : NoUnderline); } + bool fontUnderline() const; + + inline void setFontOverline(bool overline) + { setProperty(FontOverline, overline); } + inline bool fontOverline() const + { return boolProperty(FontOverline); } + + inline void setFontStrikeOut(bool strikeOut) + { setProperty(FontStrikeOut, strikeOut); } + inline bool fontStrikeOut() const + { return boolProperty(FontStrikeOut); } + + inline void setUnderlineColor(const QColor &color) + { setProperty(TextUnderlineColor, color); } + inline QColor underlineColor() const + { return colorProperty(TextUnderlineColor); } + + inline void setFontFixedPitch(bool fixedPitch) + { setProperty(FontFixedPitch, fixedPitch); } + inline bool fontFixedPitch() const + { return boolProperty(FontFixedPitch); } + + inline void setFontStyleHint(QFont::StyleHint hint, QFont::StyleStrategy strategy = QFont::PreferDefault) + { setProperty(FontStyleHint, hint); setProperty(FontStyleStrategy, strategy); } + inline void setFontStyleStrategy(QFont::StyleStrategy strategy) + { setProperty(FontStyleStrategy, strategy); } + QFont::StyleHint fontStyleHint() const + { return static_cast(intProperty(FontStyleHint)); } + QFont::StyleStrategy fontStyleStrategy() const + { return static_cast(intProperty(FontStyleStrategy)); } + + inline void setFontHintingPreference(QFont::HintingPreference hintingPreference) + { + setProperty(FontHintingPreference, hintingPreference); + } + + inline QFont::HintingPreference fontHintingPreference() const + { + return static_cast(intProperty(FontHintingPreference)); + } + + inline void setFontKerning(bool enable) + { setProperty(FontKerning, enable); } + inline bool fontKerning() const + { return boolProperty(FontKerning); } + + void setUnderlineStyle(UnderlineStyle style); + inline UnderlineStyle underlineStyle() const + { return static_cast(intProperty(TextUnderlineStyle)); } + + inline void setVerticalAlignment(VerticalAlignment alignment) + { setProperty(TextVerticalAlignment, alignment); } + inline VerticalAlignment verticalAlignment() const + { return static_cast(intProperty(TextVerticalAlignment)); } + + inline void setTextOutline(const QPen &pen) + { setProperty(TextOutline, pen); } + inline QPen textOutline() const + { return penProperty(TextOutline); } + + inline void setToolTip(const QString &tip) + { setProperty(TextToolTip, tip); } + inline QString toolTip() const + { return stringProperty(TextToolTip); } + + inline void setAnchor(bool anchor) + { setProperty(IsAnchor, anchor); } + inline bool isAnchor() const + { return boolProperty(IsAnchor); } + + inline void setAnchorHref(const QString &value) + { setProperty(AnchorHref, value); } + inline QString anchorHref() const + { return stringProperty(AnchorHref); } + + inline void setAnchorName(const QString &name) + { setAnchorNames(QStringList(name)); } + QString anchorName() const; + + inline void setAnchorNames(const QStringList &names) + { setProperty(AnchorName, names); } + QStringList anchorNames() const; + + inline void setTableCellRowSpan(int tableCellRowSpan); + inline int tableCellRowSpan() const + { int s = intProperty(TableCellRowSpan); if (s == 0) s = 1; return s; } + inline void setTableCellColumnSpan(int tableCellColumnSpan); + inline int tableCellColumnSpan() const + { int s = intProperty(TableCellColumnSpan); if (s == 0) s = 1; return s; } + +protected: + explicit QTextCharFormat(const QTextFormat &fmt); + friend class QTextFormat; +}; + +inline void QTextCharFormat::setTableCellRowSpan(int _tableCellRowSpan) +{ + if (_tableCellRowSpan <= 1) + clearProperty(TableCellRowSpan); // the getter will return 1 here. + else + setProperty(TableCellRowSpan, _tableCellRowSpan); +} + +inline void QTextCharFormat::setTableCellColumnSpan(int _tableCellColumnSpan) +{ + if (_tableCellColumnSpan <= 1) + clearProperty(TableCellColumnSpan); // the getter will return 1 here. + else + setProperty(TableCellColumnSpan, _tableCellColumnSpan); +} + +class Q_GUI_EXPORT QTextBlockFormat : public QTextFormat +{ +public: + enum LineHeightTypes { + SingleHeight = 0, + ProportionalHeight = 1, + FixedHeight = 2, + MinimumHeight = 3, + LineDistanceHeight = 4 + }; + + QTextBlockFormat(); + + bool isValid() const { return isBlockFormat(); } + + inline void setAlignment(Qt::Alignment alignment); + inline Qt::Alignment alignment() const + { int a = intProperty(BlockAlignment); if (a == 0) a = Qt::AlignLeft; return QFlag(a); } + + inline void setTopMargin(qreal margin) + { setProperty(BlockTopMargin, margin); } + inline qreal topMargin() const + { return doubleProperty(BlockTopMargin); } + + inline void setBottomMargin(qreal margin) + { setProperty(BlockBottomMargin, margin); } + inline qreal bottomMargin() const + { return doubleProperty(BlockBottomMargin); } + + inline void setLeftMargin(qreal margin) + { setProperty(BlockLeftMargin, margin); } + inline qreal leftMargin() const + { return doubleProperty(BlockLeftMargin); } + + inline void setRightMargin(qreal margin) + { setProperty(BlockRightMargin, margin); } + inline qreal rightMargin() const + { return doubleProperty(BlockRightMargin); } + + inline void setTextIndent(qreal aindent) + { setProperty(TextIndent, aindent); } + inline qreal textIndent() const + { return doubleProperty(TextIndent); } + + inline void setIndent(int indent); + inline int indent() const + { return intProperty(BlockIndent); } + + inline void setLineHeight(qreal height, int heightType) + { setProperty(LineHeight, height); setProperty(LineHeightType, heightType); } + inline qreal lineHeight(qreal scriptLineHeight, qreal scaling) const; + inline qreal lineHeight() const + { return doubleProperty(LineHeight); } + inline int lineHeightType() const + { return intProperty(LineHeightType); } + + inline void setNonBreakableLines(bool b) + { setProperty(BlockNonBreakableLines, b); } + inline bool nonBreakableLines() const + { return boolProperty(BlockNonBreakableLines); } + + inline void setPageBreakPolicy(PageBreakFlags flags) + { setProperty(PageBreakPolicy, int(flags)); } + inline PageBreakFlags pageBreakPolicy() const + { return PageBreakFlags(intProperty(PageBreakPolicy)); } + + void setTabPositions(const QList &tabs); + QList tabPositions() const; + +protected: + explicit QTextBlockFormat(const QTextFormat &fmt); + friend class QTextFormat; +}; + +inline void QTextBlockFormat::setAlignment(Qt::Alignment aalignment) +{ setProperty(BlockAlignment, int(aalignment)); } + +inline void QTextBlockFormat::setIndent(int aindent) +{ setProperty(BlockIndent, aindent); } + +inline qreal QTextBlockFormat::lineHeight(qreal scriptLineHeight, qreal scaling = 1.0) const +{ + switch(intProperty(LineHeightType)) { + case SingleHeight: + return(scriptLineHeight); + case ProportionalHeight: + return(scriptLineHeight * doubleProperty(LineHeight) / 100.0); + case FixedHeight: + return(doubleProperty(LineHeight) * scaling); + case MinimumHeight: + return(qMax(scriptLineHeight, doubleProperty(LineHeight) * scaling)); + case LineDistanceHeight: + return(scriptLineHeight + doubleProperty(LineHeight) * scaling); + } + return(0); +} + +class Q_GUI_EXPORT QTextListFormat : public QTextFormat +{ +public: + QTextListFormat(); + + bool isValid() const { return isListFormat(); } + + enum Style { + ListDisc = -1, + ListCircle = -2, + ListSquare = -3, + ListDecimal = -4, + ListLowerAlpha = -5, + ListUpperAlpha = -6, + ListLowerRoman = -7, + ListUpperRoman = -8, + ListStyleUndefined = 0 + }; + + inline void setStyle(Style style); + inline Style style() const + { return static_cast
blah