summaryrefslogtreecommitdiffstats
path: root/src/gui/text
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@nokia.com>2009-03-23 10:18:55 +0100
committerSimon Hausmann <simon.hausmann@nokia.com>2009-03-23 10:18:55 +0100
commite5fcad302d86d316390c6b0f62759a067313e8a9 (patch)
treec2afbf6f1066b6ce261f14341cf6d310e5595bc1 /src/gui/text
Long live Qt 4.5!
Diffstat (limited to 'src/gui/text')
-rw-r--r--src/gui/text/qabstractfontengine_p.h110
-rw-r--r--src/gui/text/qabstractfontengine_qws.cpp776
-rw-r--r--src/gui/text/qabstractfontengine_qws.h221
-rw-r--r--src/gui/text/qabstracttextdocumentlayout.cpp622
-rw-r--r--src/gui/text/qabstracttextdocumentlayout.h149
-rw-r--r--src/gui/text/qabstracttextdocumentlayout_p.h100
-rw-r--r--src/gui/text/qcssparser.cpp2808
-rw-r--r--src/gui/text/qcssparser_p.h835
-rw-r--r--src/gui/text/qcssscanner.cpp1146
-rw-r--r--src/gui/text/qfont.cpp3018
-rw-r--r--src/gui/text/qfont.h354
-rw-r--r--src/gui/text/qfont_mac.cpp158
-rw-r--r--src/gui/text/qfont_p.h278
-rw-r--r--src/gui/text/qfont_qws.cpp134
-rw-r--r--src/gui/text/qfont_win.cpp178
-rw-r--r--src/gui/text/qfont_x11.cpp370
-rw-r--r--src/gui/text/qfontdatabase.cpp2435
-rw-r--r--src/gui/text/qfontdatabase.h176
-rw-r--r--src/gui/text/qfontdatabase_mac.cpp509
-rw-r--r--src/gui/text/qfontdatabase_qws.cpp1062
-rw-r--r--src/gui/text/qfontdatabase_win.cpp1288
-rw-r--r--src/gui/text/qfontdatabase_x11.cpp2064
-rw-r--r--src/gui/text/qfontengine.cpp1623
-rw-r--r--src/gui/text/qfontengine_ft.cpp1904
-rw-r--r--src/gui/text/qfontengine_ft_p.h323
-rw-r--r--src/gui/text/qfontengine_mac.mm1701
-rw-r--r--src/gui/text/qfontengine_p.h617
-rw-r--r--src/gui/text/qfontengine_qpf.cpp1161
-rw-r--r--src/gui/text/qfontengine_qpf_p.h298
-rw-r--r--src/gui/text/qfontengine_qws.cpp625
-rw-r--r--src/gui/text/qfontengine_win.cpp1575
-rw-r--r--src/gui/text/qfontengine_win_p.h156
-rw-r--r--src/gui/text/qfontengine_x11.cpp1180
-rw-r--r--src/gui/text/qfontengine_x11_p.h177
-rw-r--r--src/gui/text/qfontengineglyphcache_p.h95
-rw-r--r--src/gui/text/qfontinfo.h87
-rw-r--r--src/gui/text/qfontmetrics.cpp1739
-rw-r--r--src/gui/text/qfontmetrics.h197
-rw-r--r--src/gui/text/qfontsubset.cpp1743
-rw-r--r--src/gui/text/qfontsubset_p.h99
-rw-r--r--src/gui/text/qfragmentmap.cpp46
-rw-r--r--src/gui/text/qfragmentmap_p.h872
-rw-r--r--src/gui/text/qpfutil.cpp66
-rw-r--r--src/gui/text/qsyntaxhighlighter.cpp618
-rw-r--r--src/gui/text/qsyntaxhighlighter.h111
-rw-r--r--src/gui/text/qtextcontrol.cpp2981
-rw-r--r--src/gui/text/qtextcontrol_p.h303
-rw-r--r--src/gui/text/qtextcontrol_p_p.h219
-rw-r--r--src/gui/text/qtextcursor.cpp2420
-rw-r--r--src/gui/text/qtextcursor.h232
-rw-r--r--src/gui/text/qtextcursor_p.h120
-rw-r--r--src/gui/text/qtextdocument.cpp2929
-rw-r--r--src/gui/text/qtextdocument.h298
-rw-r--r--src/gui/text/qtextdocument_p.cpp1600
-rw-r--r--src/gui/text/qtextdocument_p.h398
-rw-r--r--src/gui/text/qtextdocumentfragment.cpp1217
-rw-r--r--src/gui/text/qtextdocumentfragment.h92
-rw-r--r--src/gui/text/qtextdocumentfragment_p.h236
-rw-r--r--src/gui/text/qtextdocumentlayout.cpp3224
-rw-r--r--src/gui/text/qtextdocumentlayout_p.h119
-rw-r--r--src/gui/text/qtextdocumentwriter.cpp372
-rw-r--r--src/gui/text/qtextdocumentwriter.h93
-rw-r--r--src/gui/text/qtextengine.cpp2648
-rw-r--r--src/gui/text/qtextengine_mac.cpp656
-rw-r--r--src/gui/text/qtextengine_p.h608
-rw-r--r--src/gui/text/qtextformat.cpp3063
-rw-r--r--src/gui/text/qtextformat.h902
-rw-r--r--src/gui/text/qtextformat_p.h111
-rw-r--r--src/gui/text/qtexthtmlparser.cpp1881
-rw-r--r--src/gui/text/qtexthtmlparser_p.h342
-rw-r--r--src/gui/text/qtextimagehandler.cpp234
-rw-r--r--src/gui/text/qtextimagehandler_p.h80
-rw-r--r--src/gui/text/qtextlayout.cpp2453
-rw-r--r--src/gui/text/qtextlayout.h243
-rw-r--r--src/gui/text/qtextlist.cpp261
-rw-r--r--src/gui/text/qtextlist.h94
-rw-r--r--src/gui/text/qtextobject.cpp1711
-rw-r--r--src/gui/text/qtextobject.h328
-rw-r--r--src/gui/text/qtextobject_p.h103
-rw-r--r--src/gui/text/qtextodfwriter.cpp818
-rw-r--r--src/gui/text/qtextodfwriter_p.h115
-rw-r--r--src/gui/text/qtextoption.cpp414
-rw-r--r--src/gui/text/qtextoption.h161
-rw-r--r--src/gui/text/qtexttable.cpp1290
-rw-r--r--src/gui/text/qtexttable.h145
-rw-r--r--src/gui/text/qtexttable_p.h89
-rw-r--r--src/gui/text/qzip.cpp1208
-rw-r--r--src/gui/text/qzipreader_p.h119
-rw-r--r--src/gui/text/qzipwriter_p.h114
-rw-r--r--src/gui/text/text.pri177
90 files changed, 73025 insertions, 0 deletions
diff --git a/src/gui/text/qabstractfontengine_p.h b/src/gui/text/qabstractfontengine_p.h
new file mode 100644
index 0000000000..4752de5d0c
--- /dev/null
+++ b/src/gui/text/qabstractfontengine_p.h
@@ -0,0 +1,110 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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)
+ 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..3f2579a50b
--- /dev/null
+++ b/src/gui/text/qabstractfontengine_qws.cpp
@@ -0,0 +1,776 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qabstractfontengine_qws.h"
+#include "qabstractfontengine_p.h"
+
+#include <private/qtextengine_p.h>
+#include <private/qpaintengine_raster_p.h>
+
+#include <qmath.h>
+
+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<QFontDatabase::WritingSystem> 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<QFontDatabase::WritingSystem> QFontEngineInfo::writingSystems() const
+{
+ return d->writingSystems;
+}
+
+void QFontEngineInfo::setWritingSystems(const QList<QFontDatabase::WritingSystem> &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<QFontEngineInfo> 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 <classname> 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<uint> 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<QAbstractFontEngine::Fixed> 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<QRgb> colors(256);
+ for (int i=0; i<256; ++i)
+ colors[i] = qRgba(0, 0, 0, i);
+ indexed.setColorTable(colors);
+
+ for (int y=0; y<im.height(); ++y) {
+ uchar *dst = (uchar *) indexed.scanLine(y);
+ uint *src = (uint *) im.scanLine(y);
+ for (int x=0; x<im.width(); ++x)
+ dst[x] = qAlpha(src[x]);
+ }
+
+ return indexed;
+}
+
+
+QImage QProxyFontEngine::alphaMapForGlyph(glyph_t glyph)
+{
+ if (!(engineCapabilities & QAbstractFontEngine::CanRenderGlyphs_Gray))
+ return alphaMapFromPath(this, glyph);
+
+ QAbstractFontEngine::GlyphMetrics metrics = engine->glyphMetrics(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<QRgb> 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<QAbstractFontEngine::FixedPoint *>(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<uint> 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<QRasterPaintEngine*>(p);
+
+ QTransform matrix = pState->transform();
+ matrix.translate(_x, _y);
+ QFixed x = QFixed::fromReal(matrix.dx());
+ QFixed y = QFixed::fromReal(matrix.dy());
+
+ QVarLengthArray<QFixedPoint> positions;
+ QVarLengthArray<glyph_t> 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..3c6a1ea654
--- /dev/null
+++ b/src/gui/text/qabstractfontengine_qws.h
@@ -0,0 +1,221 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QABSTRACTFONTENGINE_QWS_H
+#define QABSTRACTFONTENGINE_QWS_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qvariant.h>
+#include <QtCore/qfactoryinterface.h>
+#include <QtGui/qpaintengine.h>
+#include <QtGui/qfontdatabase.h>
+
+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<QFontDatabase::WritingSystem> 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<QFontDatabase::WritingSystem> writingSystems() const;
+ void setWritingSystems(const QList<QFontDatabase::WritingSystem> &writingSystems);
+
+private:
+ QFontEngineInfoPrivate *d;
+};
+
+class QAbstractFontEngine;
+
+struct Q_GUI_EXPORT QFontEngineFactoryInterface : public QFactoryInterface
+{
+ virtual QAbstractFontEngine *create(const QFontEngineInfo &info) = 0;
+ virtual QList<QFontEngineInfo> 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<QFontEngineInfo> 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..6ad5775b8b
--- /dev/null
+++ b/src/gui/text/qabstracttextdocumentlayout.cpp
@@ -0,0 +1,622 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qabstracttextdocumentlayout.h>
+#include <qtextformat.h>
+#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 text
+
+ 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.
+
+ 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<QTextObjectInterface *>(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<QTextDocument *>(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<QTextDocument *>(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<const QTextDocument *>(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..4c376cc39a
--- /dev/null
+++ b/src/gui/text/qabstracttextdocumentlayout.h
@@ -0,0 +1,149 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QABSTRACTTEXTDOCUMENTLAYOUT_H
+#define QABSTRACTTEXTDOCUMENTLAYOUT_H
+
+#include <QtCore/qobject.h>
+#include <QtGui/qtextlayout.h>
+#include <QtGui/qtextdocument.h>
+#include <QtGui/qtextcursor.h>
+#include <QtGui/qpalette.h>
+
+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<Selection> 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 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..5674e17b58
--- /dev/null
+++ b/src/gui/text/qabstracttextdocumentlayout_p.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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<QObject> component;
+};
+typedef QHash<int, QTextObjectHandler> 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..db1e781c4d
--- /dev/null
+++ b/src/gui/text/qcssparser.cpp
@@ -0,0 +1,2808 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qcssparser_p.h"
+
+#include <qdebug.h>
+#include <qcolor.h>
+#include <qfont.h>
+#include <qfileinfo.h>
+#include <qfontmetrics.h>
+#include <qbrush.h>
+#include <qimagereader.h>
+
+#ifndef QT_NO_CSSPARSER
+
+QT_BEGIN_NAMESPACE
+
+#include "qcssscanner.cpp"
+
+using namespace QCss;
+
+const char *Scanner::tokenName(QCss::TokenType t)
+{
+ switch (t) {
+ case NONE: return "NONE";
+ case S: return "S";
+ case CDO: return "CDO";
+ case CDC: return "CDC";
+ case INCLUDES: return "INCLUDES";
+ case DASHMATCH: return "DASHMATCH";
+ case LBRACE: return "LBRACE";
+ case PLUS: return "PLUS";
+ case GREATER: return "GREATER";
+ case COMMA: return "COMMA";
+ case STRING: return "STRING";
+ case INVALID: return "INVALID";
+ case IDENT: return "IDENT";
+ case HASH: return "HASH";
+ case ATKEYWORD_SYM: return "ATKEYWORD_SYM";
+ case EXCLAMATION_SYM: return "EXCLAMATION_SYM";
+ case LENGTH: return "LENGTH";
+ case PERCENTAGE: return "PERCENTAGE";
+ case NUMBER: return "NUMBER";
+ case FUNCTION: return "FUNCTION";
+ case COLON: return "COLON";
+ case SEMICOLON: return "SEMICOLON";
+ case RBRACE: return "RBRACE";
+ case SLASH: return "SLASH";
+ case MINUS: return "MINUS";
+ case DOT: return "DOT";
+ case STAR: return "STAR";
+ case LBRACKET: return "LBRACKET";
+ case RBRACKET: return "RBRACKET";
+ case EQUAL: return "EQUAL";
+ case LPAREN: return "LPAREN";
+ case RPAREN: return "RPAREN";
+ case OR: return "OR";
+ }
+ return "";
+}
+
+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-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 },
+ { "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 },
+ { "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 },
+ { "uppercase", Value_Uppercase },
+ { "wave", Value_Wave },
+ { "window", Value_Window },
+ { "window-text", Value_WindowText },
+ { "x-large", Value_XLarge },
+ { "xx-large", Value_XXLarge }
+};
+
+QString Value::toString() const
+{
+ static int indexOfId[NumKnownValues - 1];
+ static bool hasCachedIndexes = false;
+
+ if (type == KnownIdentifier) {
+ if (!hasCachedIndexes) {
+ for (int i = 0; i < NumKnownValues - 1; ++i)
+ indexOfId[values[i].id] = i;
+
+ hasCachedIndexes = true;
+ }
+
+ 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 }
+};
+
+static bool operator<(const QString &name, const QCssKnownValue &prop)
+{
+ return QString::compare(name, QLatin1String(prop.name), Qt::CaseInsensitive) < 0;
+}
+
+static 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<Declaration> &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);
+
+ bool ok;
+ data.number = s.toDouble(&ok);
+ if (!ok)
+ data.number = 0;
+ 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<LengthData>(decl.d->parsed), f);
+ if (decl.d->values.count() < 1)
+ return 0;
+ LengthData data = lengthValue(decl.d->values.at(0));
+ decl.d->parsed = qVariantFromValue<LengthData>(data);
+ return lengthValueFromData(data,f);
+}
+
+void ValueExtractor::lengthValues(const Declaration &decl, int *m)
+{
+ if (decl.d->parsed.isValid()) {
+ QList<QVariant> v = decl.d->parsed.toList();
+ for (int i = 0; i < 4; i++)
+ m[i] = lengthValueFromData(qvariant_cast<LengthData>(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<QVariant> v;
+ for (i = 0; i < 4; i++) {
+ v += qVariantFromValue<LengthData>(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<QVariant> v = decl.d->parsed.toList();
+ return QSize(lengthValueFromData(qvariant_cast<LengthData>(v.at(0)), f),
+ lengthValueFromData(qvariant_cast<LengthData>(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<QVariant> v;
+ v << qVariantFromValue<LengthData>(x[0]) << qVariantFromValue<LengthData>(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 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(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<QColor>(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<Value> 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.toDouble() * 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 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<QString, qreal> vars;
+ QVector<QGradientStop> 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) {
+ 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.toDouble(), colorFromData(cd, pal)));
+ } else {
+ parser.next();
+ Value value;
+ parser.parseTerm(&value);
+ if (attr.compare(QLatin1String("spread"), Qt::CaseInsensitive) == 0) {
+ spread = spreads.indexOf(value.variant.toString());
+ } else {
+ vars[attr] = value.variant.toString().toDouble();
+ }
+ }
+ parser.skipSpace();
+ 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(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<BorderData>(decl.d->parsed);
+ *width = lengthValueFromData(data.width, f);
+ *style = data.style;
+ *color = brushFromData(data.color, pal);
+ 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 = qVariantFromValue<BorderData>(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 = qVariantFromValue<BorderData>(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 = qVariantFromValue<BorderData>(data);
+}
+
+static void parseShorthandBackgroundProperty(const QVector<Value> &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 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<Repeat>(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 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<Repeat>(decl.d->parsed.toInt());
+ } else {
+ *repeat = static_cast<Repeat>(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<BackgroundData>(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) {
+#if defined Q_CC_MSVC && _MSC_VER <= 1300
+ BackgroundData data;
+ data.brush = brushData;
+ data.image = *image;
+ data.repeat = *repeat;
+ data.alignment = *alignment;
+#else
+ BackgroundData data = { brushData, *image, *repeat, *alignment };
+#endif
+ decl.d->parsed = qVariantFromValue<BackgroundData>(data);
+ }
+ }
+ break;
+ case BackgroundAttachment:
+ *attachment = decl.attachmentValue();
+ break;
+ default: continue;
+ }
+ hit = true;
+ }
+ return hit;
+}
+
+static bool setFontSizeFromValue(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::Double)) {
+ font->setPointSizeF(value.variant.toDouble());
+ 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 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 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;
+}
+
+static bool setFontFamilyFromValues(const QVector<Value> &values, QFont *font)
+{
+ QString family;
+ for (int i = 0; i < values.count(); ++i) {
+ const Value &v = values.at(i);
+ if (v.type == Value::TermOperatorComma)
+ break;
+ const QString str = v.variant.toString();
+ if (str.isEmpty())
+ break;
+ family += str;
+ family += QLatin1Char(' ');
+ }
+ family = family.simplified();
+ if (family.isEmpty())
+ return false;
+ font->setFamily(family);
+ return true;
+}
+
+static void setTextDecorationFromValues(const QVector<Value> &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<Value> &values, QFont *font, int *fontSizeAdjustment)
+{
+ font->setStyle(QFont::StyleNormal);
+ font->setWeight(QFont::Normal);
+ *fontSizeAdjustment = 0;
+
+ 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()) {
+ QString fam = values.at(i).variant.toString();
+ if (!fam.isEmpty())
+ font->setFamily(fam);
+ }
+}
+
+static void setFontVariantFromValue(const 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 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 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<QColor>(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 = qVariantFromValue<int>(color.role);
+ return pal.color((QPalette::ColorRole)(color.role));
+ } else {
+ d->parsed = qVariantFromValue<QColor>(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<QBrush>(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 = qVariantFromValue<int>(data.role);
+ return pal.color((QPalette::ColorRole)(data.role));
+ } else {
+ if (data.type != BrushData::DependsOnThePalette)
+ d->parsed = qVariantFromValue<QBrush>(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<QVariant> v = d->parsed.toList();
+ for (i = 0; i < qMin(v.count(), 4); i++) {
+ if (v.at(i).type() == QVariant::Brush) {
+ c[i] = qvariant_cast<QBrush>(v.at(i));
+ } else if (v.at(i).type() == QVariant::Int) {
+ c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt()));
+ } else {
+ needParse |= (1<<i);
+ }
+ }
+ }
+ if (needParse != 0) {
+ QList<QVariant> v;
+ for (i = 0; i < qMin(d->values.count(), 4); i++) {
+ if (!(needParse & (1<<i)))
+ continue;
+ BrushData data = parseBrushValue(d->values.at(i), pal);
+ if(data.type == BrushData::Role) {
+ v += qVariantFromValue<int>(data.role);
+ c[i] = pal.color((QPalette::ColorRole)(data.role));
+ } else {
+ if (data.type != BrushData::DependsOnThePalette) {
+ v += qVariantFromValue<QBrush>(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 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<QSize>(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 = qVariantFromValue<QSize>(size);
+ return size;
+}
+
+QRect Declaration::rectValue() const
+{
+ if (d->values.count() != 1)
+ return QRect();
+
+ if (d->parsed.isValid())
+ return qvariant_cast<QRect>(d->parsed);
+
+ const 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(QLatin1String(" "), QString::SkipEmptyParts);
+ if (args.count() != 4)
+ return QRect();
+ QRect rect(args[0].toInt(), args[1].toInt(), args[2].toInt(), args[3].toInt());
+ d->parsed = qVariantFromValue<QRect>(rect);
+ return rect;
+}
+
+void Declaration::colorValues(QColor *c, const QPalette &pal) const
+{
+ int i;
+ if (d->parsed.isValid()) {
+ QList<QVariant> 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<QColor>(v.at(i));
+ } else {
+ c[i] = pal.color((QPalette::ColorRole)(v.at(i).toInt()));
+ }
+ }
+ } else {
+ QList<QVariant> v;
+ for (i = 0; i < qMin(d->values.count(), 4); i++) {
+ ColorData color = parseColorValue(d->values.at(i));
+ if(color.type == ColorData::Role) {
+ v += qVariantFromValue<int>(color.role);
+ c[i] = pal.color((QPalette::ColorRole)(color.role));
+ } else {
+ v += qVariantFromValue<QColor>(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<Repeat>(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<Repeat>(v);
+}
+
+Origin Declaration::originValue() const
+{
+ if (d->parsed.isValid())
+ return static_cast<Origin>(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<Origin>(v);
+}
+
+PositionMode Declaration::positionValue() const
+{
+ if (d->parsed.isValid())
+ return static_cast<PositionMode>(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<PositionMode>(v);
+}
+
+Attachment Declaration::attachmentValue() const
+{
+ if (d->parsed.isValid())
+ return static_cast<Attachment>(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<Attachment>(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<int>(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<TileMode>(findKnownValue(d->values.last().variant.toString(),
+ tileModes, NumKnownTileModes));
+ }
+ if (d->values[d->values.count() - 2].type == Value::Identifier) {
+ *h = static_cast<TileMode>
+ (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<QIcon>(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 = qVariantFromValue<QIcon>(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<StyleRule> universals;
+ for (int i = 0; i < styleRules.count(); ++i) {
+ const StyleRule &rule = styleRules.at(i);
+ QVector<Selector> 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<uint, StyleRule> *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<StyleRule> StyleSelector::styleRulesForNode(NodePtr node)
+{
+ QVector<StyleRule> rules;
+ if (styleSheets.isEmpty())
+ return rules;
+
+ QMap<uint, StyleRule> 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<QString, StyleRule>::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<QString, StyleRule>::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<uint, StyleRule>::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<Declaration> StyleSelector::declarationsForNode(NodePtr node, const char *extraPseudo)
+{
+ QVector<Declaration> decls;
+ QVector<StyleRule> 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<Symbol> *symbols)
+{
+ QCssScanner_Generated scanner(preprocessedInput);
+ Symbol sym;
+ int tok = scanner.lex();
+ while (tok != -1) {
+ sym.token = static_cast<QCss::TokenType>(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() + QLatin1String("/");
+ 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);
+ 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<Property>(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)
+{
+ test(COLON);
+ pseudo->negated = test(EXCLAMATION_SYM);
+ if (test(IDENT)) {
+ pseudo->name = lexem();
+ pseudo->type = static_cast<quint64>(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<Value> *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..97a0aef383
--- /dev/null
+++ b/src/gui/text/qcssparser_p.h
@@ -0,0 +1,835 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <QtCore/QStringList>
+#include <QtCore/QVector>
+#include <QtCore/QVariant>
+#include <QtCore/QPair>
+#include <QtCore/QSize>
+#include <QtCore/QMultiHash>
+#include <QtGui/QFont>
+#include <QtGui/QPalette>
+#include <QtGui/QIcon>
+#include <QtCore/QSharedData>
+
+
+#ifndef QT_NO_CSSPARSER
+
+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,
+ 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_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() : type(Invalid) {}
+ ColorData(const QColor &col) : color(col) , type(Color) {}
+ ColorData(QPalette::ColorRole r) : role(r) , type(Role) {}
+ QColor color;
+ QPalette::ColorRole role;
+ enum { Invalid, Color, Role} type;
+};
+
+struct BrushData {
+ BrushData() : type(Invalid) {}
+ BrushData(const QBrush &br) : brush(br) , 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<Selector> - x:hover, y:clicked z:checked
+// 3. QVector<BasicSelector> - y:clicked z:checked
+// 4. QVector<Declaration> - { prop1: value1; prop2: value2; }
+// 5. Declaration - prop1: value1;
+
+struct Q_GUI_EXPORT Declaration
+{
+ struct DeclarationData : public QSharedData
+ {
+ inline DeclarationData() : propertyId(UnknownProperty), important(false) {}
+ QString property;
+ Property propertyId;
+ QVector<Value> values;
+ QVariant parsed;
+ bool important;
+ };
+ QExplicitlySharedDataPointer<DeclarationData> 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 Q_GUI_EXPORT Pseudo
+{
+ Pseudo() : negated(false) { }
+ quint64 type;
+ QString name;
+ QString function;
+ bool negated;
+};
+
+struct Q_GUI_EXPORT AttributeSelector
+{
+ enum ValueMatchType {
+ NoMatch,
+ MatchEqual,
+ MatchContains,
+ MatchBeginsWith
+ };
+ inline AttributeSelector() : valueMatchCriterium(NoMatch) {}
+
+ QString name;
+ QString value;
+ ValueMatchType valueMatchCriterium;
+};
+
+struct Q_GUI_EXPORT BasicSelector
+{
+ inline BasicSelector() : relationToNext(NoRelation) {}
+
+ enum Relation {
+ NoRelation,
+ MatchNextSelectorIfAncestor,
+ MatchNextSelectorIfParent,
+ MatchNextSelectorIfPreceeds
+ };
+
+ QString elementName;
+
+ QStringList ids;
+ QVector<Pseudo> pseudos;
+ QVector<AttributeSelector> attributeSelectors;
+
+ Relation relationToNext;
+};
+
+struct Q_GUI_EXPORT Selector
+{
+ QVector<BasicSelector> basicSelectors;
+ int specificity() const;
+ quint64 pseudoClass(quint64 *negated = 0) const;
+ QString pseudoElement() const;
+};
+
+struct StyleRule;
+struct MediaRule;
+struct PageRule;
+struct ImportRule;
+
+struct Q_GUI_EXPORT ValueExtractor
+{
+ ValueExtractor(const QVector<Declaration> &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<Declaration> declarations;
+ QFont f;
+ int adjustment;
+ int fontExtracted;
+ QPalette pal;
+};
+
+struct Q_GUI_EXPORT StyleRule
+{
+ StyleRule() : order(0) { }
+ QVector<Selector> selectors;
+ QVector<Declaration> declarations;
+ int order;
+};
+
+struct Q_GUI_EXPORT MediaRule
+{
+ QStringList media;
+ QVector<StyleRule> styleRules;
+};
+
+struct Q_GUI_EXPORT PageRule
+{
+ QString selector;
+ QVector<Declaration> declarations;
+};
+
+struct Q_GUI_EXPORT ImportRule
+{
+ QString href;
+ QStringList media;
+};
+
+enum StyleSheetOrigin {
+ StyleSheetOrigin_Unspecified,
+ StyleSheetOrigin_UserAgent,
+ StyleSheetOrigin_User,
+ StyleSheetOrigin_Author,
+ StyleSheetOrigin_Inline
+};
+
+struct Q_GUI_EXPORT StyleSheet
+{
+ StyleSheet() : origin(StyleSheetOrigin_Unspecified), depth(0) { }
+ QVector<StyleRule> styleRules; //only contains rules that are not indexed
+ QVector<MediaRule> mediaRules;
+ QVector<PageRule> pageRules;
+ QVector<ImportRule> importRules;
+ StyleSheetOrigin origin;
+ int depth; // applicable only for inline style sheets
+ QMultiHash<QString, StyleRule> nameIndex;
+ QMultiHash<QString, StyleRule> 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<StyleRule> styleRulesForNode(NodePtr node);
+ QVector<Declaration> 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<StyleSheet> styleSheets;
+ QString medium;
+ Qt::CaseSensitivity nameCaseSensitivity;
+private:
+ void matchRule(NodePtr node, const StyleRule &rules, StyleSheetOrigin origin,
+ int depth, QMap<uint, StyleRule> *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() : 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<Symbol> *symbols);
+ static const char *tokenName(TokenType t);
+};
+
+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<Value> *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<Value> *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<Symbol> 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..2f3fdb790b
--- /dev/null
+++ b/src/gui/text/qcssscanner.cpp
@@ -0,0 +1,1146 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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++).toLower() : 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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() >= 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..43f5b1e4a8
--- /dev/null
+++ b/src/gui/text/qfont.cpp
@@ -0,0 +1,3018 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <private/qunicodetables_p.h>
+#include "qfont_p.h"
+#include <private/qfontengine_p.h>
+#include <private/qpainter_p.h>
+#include <private/qtextengine_p.h>
+#include <limits.h>
+
+#ifdef Q_WS_X11
+#include "qx11info_x11.h"
+#include <private/qt_x11_p.h>
+#endif
+#ifdef Q_WS_QWS
+#include "qscreen_qws.h"
+#if !defined(QT_NO_QWS_QPF2)
+#include <qfile.h>
+#include "qfontengine_qpf_p.h"
+#endif
+#endif
+
+#include <QMutexLocker>
+
+// #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);
+
+ 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<QScreen*> subScreens = qt_screen->subScreens();
+ if (!subScreens.isEmpty())
+ screen = subScreens.at(0);
+ dpi = qRound(screen->width() / (screen->physicalWidth() / qreal(25.4)));
+#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<QScreen*> subScreens = qt_screen->subScreens();
+ if (!subScreens.isEmpty())
+ screen = subScreens.at(0);
+ dpi = qRound(screen->height() / (screen->physicalHeight() / qreal(25.4)));
+#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)
+{
+ ref = 1;
+#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)
+{
+ ref = 1;
+#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;
+}
+
+#if !defined(Q_WS_MAC)
+extern QMutex *qt_fontdatabase_mutex();
+
+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 || !engineData->engines[script])
+ QFontDatabase::load(this, script);
+ return engineData->engines[script];
+}
+#else
+QFontEngine *QFontPrivate::engineForScript(int script) const
+{
+ extern QMutex *qt_fontdatabase_mutex();
+ 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 || !engineData->engine)
+ QFontDatabase::load(this, script);
+ return engineData->engine;
+}
+#endif
+
+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<QFontPrivate *>(this));
+ qreal pointSize = font.pointSizeF();
+ if (pointSize > 0)
+ font.setPointSizeF(pointSize * .7);
+ else
+ font.setPixelSize((font.pixelSize() * 7 + 5) / 10);
+ scFont = font.d;
+ 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::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 multimedia
+ \ingroup appearance
+ \ingroup shared
+ \ingroup text
+ \mainclass
+
+ 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.
+
+ Only on X11 when Qt was built without FontConfig support the XLFD (X Logical Font Description)
+ is returned; otherwise an empty string.
+
+ 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;
+ d->ref.ref();
+ }
+#ifdef Q_WS_WIN
+ if (pd->devType() == QInternal::Printer && pd->getDC())
+ d->hdc = pd->getDC();
+#endif
+}
+
+/*!
+ \internal
+*/
+QFont::QFont(QFontPrivate *data)
+ : resolve_mask(QFont::AllPropertiesResolved)
+{
+ d = data;
+ d->ref.ref();
+}
+
+/*! \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)
+ d->scFont->ref.deref();
+ d->scFont = 0;
+ return;
+ }
+
+ qAtomicDetach(d);
+}
+
+/*!
+ Constructs a font object that uses the application's default font.
+
+ \sa QApplication::setFont(), QApplication::font()
+*/
+QFont::QFont()
+ :d(QApplication::font().d), resolve_mask(0)
+{
+ d->ref.ref();
+}
+
+/*!
+ Constructs a font object with the specified \a family, \a
+ pointSize, \a weight and \a italic settings.
+
+ If \a pointSize is <= 0, it is set to 12.
+
+ 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) {
+ pointSize = 12;
+ } else {
+ resolve_mask |= QFont::SizeResolved;
+ }
+
+ if (weight < 0) {
+ weight = Normal;
+ } else {
+ resolve_mask |= QFont::WeightResolved | 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;
+ d->ref.ref();
+ resolve_mask = font.resolve_mask;
+}
+
+/*!
+ Destroys the font object and frees all allocated resources.
+*/
+QFont::~QFont()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ Assigns \a font to this font and returns a reference to it.
+*/
+QFont &QFont::operator=(const QFont &font)
+{
+ qAtomicAssign(d, font.d);
+ 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);
+}
+
+/*!
+ Sets the point size to \a pointSize. The point size must be
+ greater than zero.
+
+ \sa pointSize() setPointSizeF()
+*/
+void QFont::setPointSize(int pointSize)
+{
+ Q_ASSERT_X (pointSize > 0, "QFont::setPointSize", "point size must be greater than 0");
+
+ 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)
+{
+ Q_ASSERT_X(pointSize > 0.0, "QFont::setPointSizeF", "point size must be greater than 0");
+
+ 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 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 a font does not contain a character requested
+ to draw then Qt automatically chooses a similar looking for that contains
+ the character. This flag disables this feature.
+
+ 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.
+*/
+
+/*!
+ 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<QFont::Capitalization> (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));
+}
+
+
+/*!
+ 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
+
+ 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);
+
+ 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<QString, QStringList> QFontSubst;
+Q_GLOBAL_STATIC(QFontSubst, globalFontSubst)
+
+// create substitution dict
+static void initFontSubst()
+{
+ // default substitutions
+ static const char *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++;
+ }
+}
+
+// ### 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);
+ 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);
+ 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)
+{
+ if (!font.d->ref.deref())
+ delete font.d;
+
+ 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);
+
+ 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);
+ }
+ 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 multimedia
+ \ingroup shared
+ \ingroup text
+
+ 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)
+{ d->ref.ref(); }
+
+/*!
+ Constructs a copy of \a fi.
+*/
+QFontInfo::QFontInfo(const QFontInfo &fi)
+ : d(fi.d)
+{ d->ref.ref(); }
+
+/*!
+ Destroys the font info object.
+*/
+QFontInfo::~QFontInfo()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ Assigns the font info in \a fi.
+*/
+QFontInfo &QFontInfo::operator=(const QFontInfo &fi)
+{
+ qAtomicAssign(d, fi.d);
+ 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<QFontCache *>, theFontCache)
+
+QFontCache *QFontCache::instance()
+{
+ QFontCache *&fontCache = theFontCache()->localData();
+ if (!fontCache)
+ fontCache = new QFontCache;
+ return fontCache;
+}
+
+void QFontCache::cleanup()
+{
+ theFontCache()->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()
+{
+ {
+ EngineDataCache::Iterator it = engineDataCache.begin(),
+ end = engineDataCache.end();
+ 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::Iterator it = engineCache.begin(),
+ end = engineCache.end();
+ 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 %d %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..eec83b586b
--- /dev/null
+++ b/src/gui/text/qfont.h
@@ -0,0 +1,354 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QFONT_H
+#define QFONT_H
+
+#include <QtGui/qwindowdefs.h>
+#include <QtCore/qstring.h>
+
+#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
+ };
+
+ enum StyleStrategy {
+ PreferDefault = 0x0001,
+ PreferBitmap = 0x0002,
+ PreferDevice = 0x0004,
+ PreferOutline = 0x0008,
+ ForceOutline = 0x0010,
+ PreferMatch = 0x0020,
+ PreferQuality = 0x0040,
+ PreferAntialias = 0x0080,
+ NoAntialias = 0x0100,
+ OpenGLCompatible = 0x0200,
+ NoFontMerging = 0x8000
+ };
+
+ 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,
+ AllPropertiesResolved = 0x7fff
+ };
+
+ 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;
+
+ // 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_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 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;
+
+#ifndef QT_NO_DATASTREAM
+ friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QFont &);
+ friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QFont &);
+#endif
+
+ QFontPrivate *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..8320f714c8
--- /dev/null
+++ b/src/gui/text/qfont_mac.cpp
@@ -0,0 +1,158 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qfont.h"
+#include "qfont_p.h"
+#include "qfontengine_p.h"
+#include "qfontinfo.h"
+#include "qfontmetrics.h"
+#include "qpaintdevice.h"
+#include "qstring.h"
+#include <private/qt_mac_p.h>
+#include <private/qtextengine_p.h>
+#include <private/qunicodetables_p.h>
+#include <qapplication.h>
+#include "qfontdatabase.h"
+#include <qpainter.h>
+#include "qtextengine_p.h"
+#include <stdlib.h>
+
+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<QFontEngineMacMulti*>(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<QFontEngineMacMulti*>(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::Decorative:
+ return QString::fromLatin1("Bookman Old Style");
+ 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..4e8a7b74e3
--- /dev/null
+++ b/src/gui/text/qfont_p.h
@@ -0,0 +1,278 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <private/qunicodetables_p.h>
+#include <QtGui/qfontdatabase.h>
+#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)
+#ifdef Q_WS_MAC
+ ,fixedPitchComputed(false)
+#endif
+ {
+ }
+
+ QString family;
+
+#ifdef Q_WS_X11
+ QString addStyle;
+#endif // Q_WS_X11
+
+ qreal pointSize;
+ int 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 fixedPitchComputed : 1; // for Mac OS X only
+ int reserved : 16; // 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
+#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;
+
+#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;
+
+ 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<Key,QFontEngineData*> 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<Key,Engine> 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;
+};
+
+QT_END_NAMESPACE
+
+#endif // QFONT_P_H
diff --git a/src/gui/text/qfont_qws.cpp b/src/gui/text/qfont_qws.cpp
new file mode 100644
index 0000000000..f07341d0c0
--- /dev/null
+++ b/src/gui/text/qfont_qws.cpp
@@ -0,0 +1,134 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qwidget.h"
+#include "qpainter.h"
+#include "qfont_p.h"
+#include <private/qunicodetables_p.h>
+#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<QFontEngineMulti *>(engine)->engine(0);
+ if (engine->type() == QFontEngine::Freetype) {
+ const QFontEngineFT *ft = static_cast<const QFontEngineFT *>(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:
+ 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_win.cpp b/src/gui/text/qfont_win.cpp
new file mode 100644
index 0000000000..5db5a6847d
--- /dev/null
+++ b/src/gui/text/qfont_win.cpp
@@ -0,0 +1,178 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+// the miscrosoft platform SDK says that the Unicode versions of
+// TextOut and GetTextExtentsPoint32 are supported on all platforms, so we use them
+// exclusively to simplify code, save a lot of conversions into the local encoding
+// and have generally better unicode support :)
+
+#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 <limits.h>
+#include "qt_windows.h"
+#include <private/qapplication_p.h>
+#include "qapplication.h"
+#include <private/qunicodetables_p.h>
+#include <qfontdatabase.h>
+
+QT_BEGIN_NAMESPACE
+
+extern HDC shared_dc(); // common dc for all fonts
+
+// ### maybe move to qapplication_win
+QFont qt_LOGFONTtoQFont(LOGFONT& lf, bool /*scale*/)
+{
+ QString family = QT_WA_INLINE(QString::fromUtf16((ushort*)lf.lfFaceName),
+ QString::fromLocal8Bit((char*)lf.lfFaceName));
+ QFont qf(family);
+ qf.setItalic(lf.lfItalic);
+ if (lf.lfWeight != FW_DONTCARE) {
+ int weight;
+ if (lf.lfWeight < 400)
+ weight = QFont::Light;
+ else if (lf.lfWeight < 600)
+ weight = QFont::Normal;
+ else if (lf.lfWeight < 700)
+ weight = QFont::DemiBold;
+ else if (lf.lfWeight < 800)
+ weight = QFont::Bold;
+ else
+ weight = QFont::Black;
+ qf.setWeight(weight);
+ }
+ 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<QFontEngineMulti *>(engine)->engine(0);
+ if (engine->type() == QFontEngine::Win)
+ return static_cast<QFontEngineWin *>(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:
+ return QString::fromLatin1("Courier New");
+ case QFont::Decorative:
+ return QString::fromLatin1("Bookman Old Style");
+ 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..710792c9de
--- /dev/null
+++ b/src/gui/text/qfont_x11.cpp
@@ -0,0 +1,370 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <private/qunicodetables_p.h>
+#include "qfont_p.h"
+#include "qfontengine_p.h"
+#include "qfontengine_x11_p.h"
+#include "qtextengine_p.h"
+
+#include <private/qt_x11_p.h>
+#include "qx11info_x11.h"
+
+#include <time.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#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<int, QString> 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;
+
+/*!
+ Internal function that initializes the font system.
+
+ \internal
+ The font cache and font dict do not alloc the keys. The key is a QString
+ which is shared between QFontPrivate and QXFontName.
+*/
+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);
+}
+
+/*! \internal
+
+ Internal function that cleans up the font system.
+*/
+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<QFontEngineMulti *>(engine)->engine(0);
+ if (engine->type() == QFontEngine::XLFD)
+ return static_cast<QFontEngineXLFD *>(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<QFontEngineMulti *>(engine)->engine(0);
+#ifndef QT_NO_FONTCONFIG
+ if (engine->type() == QFontEngine::Freetype) {
+ const QFontEngineFT *ft = static_cast<const QFontEngineFT *>(engine);
+ return ft->non_locked_face();
+ } else
+#endif
+ if (engine->type() == QFontEngine::XLFD) {
+ const QFontEngineXLFD *xlfd = static_cast<const QFontEngineXLFD *>(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<QFontEngineMulti *>(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::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..c3fc9f55cb
--- /dev/null
+++ b/src/gui/text/qfontdatabase.cpp
@@ -0,0 +1,2435 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qdir.h>
+#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_X11
+#include <locale.h>
+#endif
+#include <stdlib.h>
+#include <limits.h>
+
+// #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_CC_MSVC) && !defined(Q_CC_MSVC_NET)
+# define for if(0){}else for
+#endif
+
+QT_BEGIN_NAMESPACE
+
+extern int qt_defaultDpiY(); // in qfont.cpp
+
+Q_GUI_EXPORT bool qt_enable_test_font = false;
+
+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(qApp->translate("QFontDatabase", "Normal"), Qt::CaseInsensitive) == 0)
+ return QFont::Normal;
+ if (s == QLatin1String("bold")
+ || s.compare(qApp->translate("QFontDatabase", "Bold"), Qt::CaseInsensitive) == 0)
+ return QFont::Bold;
+ if (s == QLatin1String("demibold") || s == QLatin1String("demi bold")
+ || s.compare(qApp->translate("QFontDatabase", "Demi Bold"), Qt::CaseInsensitive) == 0)
+ return QFont::DemiBold;
+ if (s == QLatin1String("black")
+ || s.compare(qApp->translate("QFontDatabase", "Black"), Qt::CaseInsensitive) == 0)
+ return QFont::Black;
+ if (s == QLatin1String("light"))
+ return QFont::Light;
+
+ if (s.contains(QLatin1String("bold"))
+ || s.contains(qApp->translate("QFontDatabase", "Bold"), Qt::CaseInsensitive)) {
+ if (s.contains(QLatin1String("demi"))
+ || s.compare(qApp->translate("QFontDatabase", "Demi"), Qt::CaseInsensitive) == 0)
+ return (int) QFont::DemiBold;
+ return (int) QFont::Bold;
+ }
+
+ if (s.contains(QLatin1String("light"))
+ || s.compare(qApp->translate("QFontDatabase", "Light"), Qt::CaseInsensitive) == 0)
+ return (int) QFont::Light;
+
+ if (s.contains(QLatin1String("black"))
+ || s.compare(qApp->translate("QFontDatabase", "Black"), Qt::CaseInsensitive) == 0)
+ return (int) QFont::Black;
+
+ return (int) QFont::Normal;
+}
+
+struct QtFontEncoding
+{
+ signed int encoding : 16;
+
+ uint xpoint : 16;
+ uint xres : 8;
+ uint yres : 8;
+ uint avgwidth : 16;
+ uchar pitch : 8;
+};
+
+struct QtFontSize
+{
+ unsigned short pixelSize;
+
+#ifdef Q_WS_X11
+ int count;
+ QtFontEncoding *encodings;
+ QtFontEncoding *encodingID(int id, uint xpoint = 0, uint xres = 0,
+ uint yres = 0, uint avgwidth = 0, bool add = false);
+#endif // Q_WS_X11
+#ifdef Q_WS_QWS
+ QByteArray fileName;
+ int fileIndex;
+#endif
+};
+
+
+#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))
+ encodings = (QtFontEncoding *)
+ realloc(encodings,
+ (((count+4) >> 2) << 2) * sizeof(QtFontEncoding));
+ 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)
+ while (count--) {
+#ifdef Q_WS_X11
+ free(pixelSizes[count].encodings);
+#endif
+#ifdef Q_WS_QWS
+ pixelSizes[count].fileName.~QByteArray();
+#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
+#ifdef Q_WS_QWS
+ 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(qApp->translate("QFontDatabase", "Italic")))
+ style = QFont::StyleItalic;
+ else if (styleString.contains(QLatin1String("Oblique"))
+ || styleString.contains(qApp->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 (!(count % 8))
+ pixelSizes = (QtFontSize *)
+ realloc(pixelSizes,
+ (((count+8) >> 3) << 3) * sizeof(QtFontSize));
+ pixelSizes[count].pixelSize = size;
+#ifdef Q_WS_X11
+ pixelSizes[count].count = 0;
+ pixelSizes[count].encodings = 0;
+#endif
+#ifdef Q_WS_QWS
+ new (&pixelSizes[count].fileName) QByteArray;
+ pixelSizes[count].fileIndex = 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))
+ styles = (QtFontStyle **)
+ realloc(styles, (((count+8) >> 3) << 3) * sizeof(QtFontStyle *));
+
+ memmove(styles + pos + 1, styles + pos, (count-pos)*sizeof(QtFontStyle *));
+ styles[pos] = new QtFontStyle(key);
+ 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)
+ , bogusWritingSystems(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;
+#endif
+
+ QString name;
+#ifdef Q_WS_X11
+ QByteArray fontFilename;
+ int fontFileIndex;
+#endif
+#ifdef Q_WS_WIN
+ QString english_name;
+#endif
+ int count;
+ QtFontFoundry **foundries;
+
+#ifdef Q_WS_QWS
+ bool bogusWritingSystems;
+ QStringList fallbackFamilies;
+#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))
+ foundries = (QtFontFoundry **)
+ realloc(foundries,
+ (((count+8) >> 3) << 3) * sizeof(QtFontFoundry *));
+
+ foundries[count] = new QtFontFoundry(f);
+ return foundries[count++];
+}
+
+// ### copied to tools/makeqpf/qpf2.cpp
+
+#if (defined(Q_WS_QWS) && !defined(QT_NO_FREETYPE)) || defined(Q_WS_WIN) || defined(Q_WS_MAC)
+// 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 }
+};
+
+#define SimplifiedChineseCsbBit 18
+#define TraditionalChineseCsbBit 20
+#define JapaneseCsbBit 17
+#define KoreanCsbBit 21
+
+static QList<QFontDatabase::WritingSystem> determineWritingSystemsFromTrueTypeBits(quint32 unicodeRange[4], quint32 codePageRange[2])
+{
+ QList<QFontDatabase::WritingSystem> 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;
+}
+#endif
+
+class QFontDatabasePrivate
+{
+public:
+ QFontDatabasePrivate()
+ : count(0), families(0), reregisterAppFonts(false)
+#if defined(Q_WS_QWS)
+ , stream(0)
+#endif
+ { }
+ ~QFontDatabasePrivate() {
+ free();
+ }
+ 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;
+ QtFontFamily **families;
+
+ struct ApplicationFont {
+ QString fileName;
+ QByteArray data;
+#if defined(Q_OS_WIN)
+ HANDLE handle;
+ bool memoryFont;
+ QVector<FONTSIGNATURE> signatures;
+#elif defined(Q_WS_MAC)
+ ATSFontContainerRef handle;
+#endif
+ QStringList families;
+ };
+ QVector<ApplicationFont> 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 addFont(const QString &familyname, const char *foundryname, int weight,
+ bool italic, int pixelSize, const QByteArray &file, int fileIndex,
+ bool antialiased,
+ const QList<QFontDatabase::WritingSystem> &writingSystems = QList<QFontDatabase::WritingSystem>());
+ void addQPF2File(const QByteArray &file);
+#ifndef QT_NO_FREETYPE
+ QStringList addTTFile(const QByteArray &file, const QByteArray &fontData = QByteArray());
+#endif
+
+ QDataStream *stream;
+ QStringList fallbackFamilies;
+#endif
+};
+
+void QFontDatabasePrivate::invalidate()
+{
+ QFontCache::instance()->clear();
+ free();
+ emit static_cast<QApplication *>(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))
+ families = (QtFontFamily **)
+ realloc(families,
+ (((count+8) >> 3) << 3) * sizeof(QtFontFamily *));
+
+ memmove(families + pos + 1, families + pos, (count-pos)*sizeof(QtFontFamily *));
+ families[pos] = new QtFontFamily(f);
+ count++;
+ return families[pos];
+}
+
+
+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
+};
+
+
+#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);
+}
+static inline bool scriptRequiresOpenType(int script)
+{
+ return ((script >= QUnicodeTables::Syriac && script <= QUnicodeTables::Sinhala)
+ || script == QUnicodeTables::Khmer);
+}
+#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<int> &blacklistedFamilies = QList<int>());
+
+#if defined(Q_WS_X11) || defined(Q_WS_QWS)
+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 += QString::fromLatin1("]");
+ }
+
+ 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)
+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();
+ }
+}
+
+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;
+}
+#endif
+
+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();
+}
+
+#define SMOOTH_SCALABLE 0xffff
+
+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"
+#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<QtFontFamily*>(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<int> &blacklistedFamilies)
+{
+ 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: %d\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;
+
+ load(family_name, script);
+
+ 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 = qApp->translate("QFontDatabase", "Black");
+ else if (weight >= QFont::Bold)
+ result = qApp->translate("QFontDatabase", "Bold");
+ else if (weight >= QFont::DemiBold)
+ result = qApp->translate("QFontDatabase", "Demi Bold");
+ else if (weight < QFont::Normal)
+ result = qApp->translate("QFontDatabase", "Light");
+
+ if (style == QFont::StyleItalic)
+ result += QLatin1Char(' ') + qApp->translate("QFontDatabase", "Italic");
+ else if (style == QFont::StyleOblique)
+ result += QLatin1Char(' ') + qApp->translate("QFontDatabase", "Oblique");
+
+ if (result.isEmpty())
+ result = qApp->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 environment
+ \ingroup multimedia
+ \ingroup text
+
+ 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, e.g. "Helvetica
+ [Adobe]" and "Helvetica [Cronyx]". When you specify a family you
+ can either use the old hyphenated Qt 2.x "foundry-family" format,
+ e.g. "Cronyx-Helvetica", or the new bracketed Qt 3.x "family
+ [foundry]" format e.g. "Helvetica [Cronyx]". If the family has a
+ foundry it is always returned, e.g. by families(), using the
+ bracketed format.
+
+ 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
+
+ \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::WritingSystem> QFontDatabase::writingSystems() const
+{
+ QMutexLocker locker(fontDatabaseMutex());
+
+ QT_PREPEND_NAMESPACE(load)();
+#ifdef Q_WS_X11
+ checkSymbolFonts();
+#endif
+
+ QList<WritingSystem> 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::WritingSystem> 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<WritingSystem> 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 += QLatin1String("]");
+ }
+ 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<int> 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<int> 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<int> 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<int> 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<int> QFontDatabase::standardSizes()
+{
+ QList<int> 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;
+ default:
+ Q_ASSERT_X(false, "QFontDatabase::writingSystemName", "invalid 'writingSystem' parameter");
+ break;
+ }
+ return qApp ? qApp->translate("QFontDatabase", name) : QString::fromLatin1(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(0x3050);
+ sample += QChar(0x3060);
+ sample += QChar(0x30b0);
+ sample += QChar(0x30c0);
+ break;
+ case Korean:
+ sample += QChar(0xac00);
+ sample += QChar(0xac11);
+ sample += QChar(0xac1a);
+ sample += QChar(0xac2f);
+ break;
+ case Vietnamese:
+ 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;
+ 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
+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.
+
+ \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.
+
+ \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 threads.html#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..eadb6babdb
--- /dev/null
+++ b/src/gui/text/qfontdatabase.h
@@ -0,0 +1,176 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QFONTDATABASE_H
+#define QFONTDATABASE_H
+
+#include <QtGui/qwindowdefs.h>
+#include <QtCore/qstring.h>
+#include <QtGui/qfont.h>
+#ifdef QT3_SUPPORT
+#include <QtCore/qstringlist.h>
+#include <QtCore/qlist.h>
+#endif
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Gui)
+
+class QStringList;
+template <class T> 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,
+
+ WritingSystemsCount
+ };
+
+ static QList<int> standardSizes();
+
+ QFontDatabase();
+
+ QList<WritingSystem> writingSystems() const;
+ QList<WritingSystem> writingSystems(const QString &family) const;
+
+ QStringList families(WritingSystem writingSystem = Any) const;
+ QStringList styles(const QString &family) const;
+ QList<int> pointSizes(const QString &family, const QString &style = QString());
+ QList<int> 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);
+#if defined(Q_WS_QWS)
+ 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;
+
+ 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..80ddbd5c60
--- /dev/null
+++ b/src/gui/text/qfontdatabase_mac.cpp
@@ -0,0 +1,509 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <private/qt_mac_p.h>
+#include "qfontengine_p.h"
+#include <qfile.h>
+#include <qabstractfileengine.h>
+#include <stdlib.h>
+#include <qendian.h>
+
+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
+
+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<uchar> 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<quint32>(os2Table.data() + 42),
+ qFromBigEndian<quint32>(os2Table.data() + 46),
+ qFromBigEndian<quint32>(os2Table.data() + 50),
+ qFromBigEndian<quint32>(os2Table.data() + 54)
+ };
+ quint32 codePageRange[2] = { qFromBigEndian<quint32>(os2Table.data() + 78), qFromBigEndian<quint32>(os2Table.data() + 82) };
+ QList<QFontDatabase::WritingSystem> systems = determineWritingSystemsFromTrueTypeBits(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;
+}
+
+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<CTFontCollectionRef> collection = CTFontCollectionCreateFromAvailableFonts(0);
+ if(!collection)
+ return;
+ QCFType<CFArrayRef> 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<CFDictionaryRef> 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<CFNumberRef> 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 Q_WS_MAC64
+ 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<quint16>(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;
+}
+
+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 = req.family.split(QLatin1Char(','));
+ // append the substitute list for each family in family_list
+ {
+ QStringList subs_list;
+ for(QStringList::ConstIterator it = family_list.constBegin(); it != family_list.constEnd(); ++it)
+ subs_list += QFont::substitutes(*it);
+ family_list += subs_list;
+ }
+
+ 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();
+
+ ATSFontFamilyRef familyRef = 0;
+ ATSFontRef fontRef = 0;
+
+ 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();
+ familyRef = ATSFontFamilyFindFromName(QCFString(db->families[k]->name), kATSOptionFlagsDefault);
+ if (familyRef) {
+ fontRef = ATSFontFindFromName(QCFString(db->families[k]->name), kATSOptionFlagsDefault);
+ goto FamilyFound;
+ }
+ }
+ }
+ }
+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);
+#if 0
+ ItemCount name_count;
+ if(ATSUCountFontNames(fontID, &name_count) == noErr && name_count) {
+ ItemCount actualName_size;
+ if(ATSUGetIndFontName(fontID, 0, 0, 0, &actualName_size, 0, 0, 0, 0) == noErr && actualName_size) {
+ QByteArray actualName(actualName_size);
+ if(ATSUGetIndFontName(fontID, 0, actualName_size, actualName.data(), &actualName_size, 0, 0, 0, 0) == noErr && actualName_size)
+ fontDef.family = QString::fromUtf8(actualName);
+ }
+ }
+#else
+ {
+ QCFString actualName;
+ if(ATSFontFamilyGetName(familyRef, kATSOptionFlagsDefault, &actualName) == noErr)
+ fontDef.family = actualName;
+ }
+#endif
+
+#ifdef QT_MAC_USE_COCOA
+ QFontEngine *engine = new QCoreTextFontEngineMulti(familyRef, fontRef, fontDef, d->kerning);
+#elif 1
+ QFontEngine *engine = new QFontEngineMacMulti(familyRef, fontRef, fontDef, d->kerning);
+#else
+ ATSFontFamilyRef atsFamily = familyRef;
+ ATSFontFamilyRef atsFontRef = fontRef;
+
+ FMFont fontID;
+ 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);
+ }
+
+ OSStatus status;
+
+ 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] = &fontID;
+ ++attributeCount;
+
+ CGAffineTransform 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;
+ }
+
+ ATSUStyle style;
+ status = ATSUCreateStyle(&style);
+ Q_ASSERT(status == noErr);
+
+ Q_ASSERT(attributeCount < maxAttributeCount + 1);
+ status = ATSUSetAttributes(style, attributeCount, tags, sizes, values);
+ Q_ASSERT(status == noErr);
+
+ QFontEngine *engine = new QFontEngineMac(style, fontID, fontDef, /*multiEngine*/ 0);
+ ATSUDisposeStyle(style);
+#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<ATSFontRef> containedFonts(fontCount);
+ e = ATSFontFindFromContainer(handle, kATSOptionFlagsDefault, fontCount, containedFonts.data(), &fontCount);
+ if(e != noErr)
+ return;
+
+ fnt->families.clear();
+ for(int i = 0; i < containedFonts.size(); ++i) {
+ QCFString family;
+ ATSFontGetName(containedFonts[i], kATSOptionFlagsDefault, &family);
+ fnt->families.append(family);
+ }
+
+ 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_qws.cpp b/src/gui/text/qfontdatabase_qws.cpp
new file mode 100644
index 0000000000..eb8a0cf5c2
--- /dev/null
+++ b/src/gui/text/qfontdatabase_qws.cpp
@@ -0,0 +1,1062 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qdir.h"
+#include "qscreen_qws.h" //so we can check for rotation
+#include "qwindowsystem_qws.h"
+#include "qlibraryinfo.h"
+#include "qabstractfileengine.h"
+#include <QtCore/qsettings.h>
+#if !defined(QT_NO_FREETYPE)
+#include "qfontengine_ft_p.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_TRUETYPE_TABLES_H
+
+#endif
+#include "qfontengine_qpf_p.h"
+#include "private/qfactoryloader_p.h"
+#include "qabstractfontengine_qws.h"
+#include "qabstractfontengine_p.h"
+#include <qdatetime.h>
+
+// for mmap
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef QT_FONTS_ARE_RESOURCES
+#include <qresource.h>
+#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;
+
+void QFontDatabasePrivate::addFont(const QString &familyname, const char *foundryname, int weight, bool italic, int pixelSize,
+ const QByteArray &file, int fileIndex, bool antialiased,
+ const QList<QFontDatabase::WritingSystem> &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 (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));
+ }
+}
+
+#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 = ::open(file, O_RDONLY);
+ 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<QFont::Style>(style.toInt()) & QFont::StyleItalic;
+
+ QList<QFontDatabase::WritingSystem> 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
+ ::close(f);
+#endif
+}
+#endif
+
+#ifndef 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<QFontDatabase::WritingSystem> 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 = determineWritingSystemsFromTrueTypeBits(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 void registerFont(QFontDatabasePrivate::ApplicationFont *fnt);
+
+extern QString qws_fontCacheDir();
+
+#ifndef QT_FONTS_ARE_RESOURCES
+bool QFontDatabasePrivate::loadFromCache(const QString &fontPath)
+{
+ const bool weAreTheServer = QWSServer::instance();
+
+ 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<QFontDatabase::WritingSystem> 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;
+}
+
+#ifdef QFONTDATABASE_DEBUG
+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; i<int(dir.count()); i++) {
+ int u0 = dir[i].indexOf(QLatin1Char('_'));
+ int u1 = dir[i].indexOf(QLatin1Char('_'), u0+1);
+ int u2 = dir[i].indexOf(QLatin1Char('_'), u1+1);
+ int u3 = dir[i].indexOf(QLatin1Char('.'), u1+1);
+ if (u2 < 0) u2 = u3;
+
+ QString familyname = dir[i].left(u0);
+ int pixelSize = dir[i].mid(u0+1,u1-u0-1).toInt()/10;
+ bool italic = dir[i].mid(u2-1,1) == QLatin1String("i");
+ int weight = dir[i].mid(u1+1,u2-u1-1-(italic?1:0)).toInt();
+
+ db->addFont(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<FriendlyResource*>(&fontdir);
+ qDebug() << "fontdir" << fr->isValid() << fr->isDir() << fr->children();
+
+ }
+#endif
+ 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 //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<QFontEngineFactoryInterface *>(loader()->instance(foundry));
+ if (!factory) {
+ qDebug() << "Could not load plugin for foundry" << foundry;
+ continue;
+ }
+
+ QList<QFontEngineInfo> 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;
+ qDebug() << "fontengine is not valid! " << size->fileName;
+ delete fe;
+ } else {
+ qDebug() << "Resource not valid" << size->fileName;
+ }
+#else
+ int f = ::open(size->fileName, O_RDONLY);
+ if (f >= 0) {
+ QFontEngineQPF *fe = new QFontEngineQPF(request, f);
+ if (fe->isValid())
+ return fe;
+ qDebug() << "fontengine is not valid!";
+ delete fe; // will close f
+ }
+#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;
+
+ static bool dontShareFonts = !qgetenv("QWS_NO_SHARE_FONTS").isEmpty();
+ bool shareFonts = !dontShareFonts;
+
+ QFontEngine *engine = 0;
+
+#ifndef QT_NO_LIBRARY
+ QFontEngineFactoryInterface *factory = qobject_cast<QFontEngineFactoryInterface *>(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 = 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 && !file.isEmpty() && QFile::exists(file) || privateDb()->isApplicationFont(file)) {
+ QFontEngine::FaceId faceId;
+ faceId.filename = file.toLocal8Bit();
+ faceId.index = size->fileIndex;
+
+#ifndef QT_NO_FREETYPE
+
+ QFontEngineFT *fte = new QFontEngineFT(def);
+ if (fte->init(faceId, style->antialiased,
+ style->antialiased ? QFontEngineFT::Format_A8 : QFontEngineFT::Format_Mono)) {
+#ifdef QT_NO_QWS_QPF2
+ return fte;
+#else
+ engine = fte;
+ // 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();
+#endif
+ } else {
+ delete fte;
+ }
+#endif // QT_NO_FREETYPE
+ }
+ if (engine) {
+#if !defined(QT_NO_QWS_QPF2) && !defined(QT_FONTS_ARE_RESOURCES)
+ if (shareFonts) {
+ QFontEngineQPF *fe = new QFontEngineQPF(def, -1, engine);
+ engine = 0;
+ if (fe->isValid())
+ return fe;
+ qWarning("Initializing QFontEngineQPF failed for %s", qPrintable(file));
+ engine = fe->takeRenderingEngine();
+ delete fe;
+ }
+#endif
+ return engine;
+ }
+ } else
+ {
+#ifndef QT_NO_QWS_QPF
+ QString fn = qwsFontPath();
+ fn += QLatin1Char('/');
+ fn += family->name.toLower()
+ + QLatin1String("_") + QString::number(pixelSize*10)
+ + QLatin1String("_") + 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)
+{
+ QFontEngine *fe = loadSingleEngine(script, fp, request, family, foundry,
+ style, size);
+ if (fe
+ && script == QUnicodeTables::Common
+ && !(request.styleStrategy & QFont::NoFontMerging) && !fe->symbol) {
+
+ QStringList fallbacks = privateDb()->fallbackFamilies;
+
+ if (family && !family->fallbackFamilies.isEmpty())
+ fallbacks = family->fallbackFamilies;
+
+ fe = new QFontEngineMultiQWS(fe, script, fallbacks);
+ }
+ return fe;
+}
+
+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();
+
+ QFontEngine *fe = 0;
+ if (fp) {
+ if (fp->rawMode) {
+ fe = loadEngine(script, fp, request, 0, 0, 0, 0
+ );
+
+ // if we fail to load the rawmode font, use a 12pixel box engine instead
+ if (! fe) fe = new QFontEngineBox(12);
+ return fe;
+ }
+
+ QFontCache::Key key(request, script);
+ fe = QFontCache::instance()->findEngine(key);
+ if (fe)
+ return fe;
+ }
+
+ 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: %d\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 = new QTestFontEngine(request.pixelSize);
+ fe->fontDef = request;
+ }
+
+ if (!fe)
+ {
+ 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 = loadEngine(script, fp, request, desc.family, desc.foundry, desc.style, desc.size
+ );
+ } else {
+ FM_DEBUG(" NO MATCH FOUND\n");
+ }
+ if (fe)
+ initFontDef(desc, request, &fe->fontDef);
+ }
+
+ if (fe) {
+ 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);
+ }
+
+#ifndef QT_NO_FREETYPE
+ if (scriptRequiresOpenType(script) && fe->type() == QFontEngine::Freetype) {
+ HB_Face hbFace = static_cast<QFontEngineFT *>(fe)->harfbuzzFace();
+ if (!hbFace || !hbFace->supported_scripts[script]) {
+ FM_DEBUG(" OpenType support missing for script\n");
+ delete fe;
+ fe = 0;
+ }
+ }
+#endif
+ }
+
+ if (!fe) {
+ if (!request.family.isEmpty())
+ return 0;
+
+ FM_DEBUG("returning box engine");
+
+ fe = new QFontEngineBox(request.pixelSize);
+
+ if (fp) {
+ QFontCache::Key key(request, script);
+ QFontCache::instance()->insertEngine(key, fe);
+ }
+ }
+
+ if (fp && fp->dpi > 0) {
+ fe->fontDef.pointSize = qreal(double((fe->fontDef.pixelSize * 72) / fp->dpi));
+ } else {
+ fe->fontDef.pointSize = request.pointSize;
+ }
+
+ return fe;
+}
+
+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;
+ QFontCache::instance()->insertEngineData(key, d->engineData);
+ } else {
+ d->engineData->ref.ref();
+ }
+ }
+
+ // the cached engineData could have already loaded the engine we want
+ if (d->engineData->engines[script]) return;
+
+ // load the font
+ QFontEngine *engine = 0;
+ // 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();
+
+ 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) {
+ if (engine->type() != QFontEngine::Box)
+ break;
+
+ if (! req.family.isEmpty())
+ engine = 0;
+
+ continue;
+ }
+ }
+
+ engine->ref.ref();
+ d->engineData->engines[script] = engine;
+}
+
+
+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..c9f558664d
--- /dev/null
+++ b/src/gui/text/qfontdatabase_win.cpp
@@ -0,0 +1,1288 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qt_windows.h"
+#include <private/qapplication_p.h>
+#include "qfont_p.h"
+#include "qfontengine_p.h"
+#include "qpaintdevice.h"
+#include "qlibrary.h"
+#include "qabstractfileengine.h"
+#include "qendian.h"
+
+#ifdef Q_OS_WINCE
+# include <QTemporaryFile>
+#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 );
+ HFONT hfont;
+ QT_WA( {
+ LOGFONTW lf;
+ memset( &lf, 0, sizeof( LOGFONTW ) );
+ memcpy( lf.lfFaceName, familyName.utf16(), qMin(LF_FACESIZE, familyName.length())*sizeof(QChar) );
+ lf.lfCharSet = DEFAULT_CHARSET;
+ hfont = CreateFontIndirectW( &lf );
+ }, {
+ LOGFONTA lf;
+ memset( &lf, 0, sizeof( LOGFONTA ) );
+ QByteArray lfam = familyName.toLocal8Bit();
+ memcpy( lf.lfFaceName, lfam, qMin(LF_FACESIZE, lfam.size()) );
+ lf.lfCharSet = DEFAULT_CHARSET;
+ hfont = CreateFontIndirectA( &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;
+}
+
+static void getFontSignature(const QString &familyName,
+ NEWTEXTMETRICEX *textmetric,
+ FONTSIGNATURE *signature)
+{
+ QT_WA({
+ Q_UNUSED(familyName);
+ *signature = textmetric->ntmFontSig;
+ }, {
+ // the textmetric structure we get from EnumFontFamiliesEx on Win9x has
+ // a FONTSIGNATURE, but that one is uninitialized and doesn't work. Have to go
+ // the hard way and load the font to find out.
+ HDC hdc = GetDC(0);
+ LOGFONTA lf;
+ memset(&lf, 0, sizeof(LOGFONTA));
+ QByteArray lfam = familyName.toLocal8Bit();
+ memcpy(lf.lfFaceName, lfam.data(), qMin(LF_FACESIZE, lfam.length()));
+ lf.lfCharSet = DEFAULT_CHARSET;
+ HFONT hfont = CreateFontIndirectA(&lf);
+ HGDIOBJ oldobj = SelectObject(hdc, hfont);
+ GetTextCharsetInfo(hdc, signature, 0);
+ SelectObject(hdc, oldobj);
+ DeleteObject(hfont);
+ ReleaseDC(0, hdc);
+ });
+}
+
+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::fromUtf16((ushort *)f->elfScript);
+// qDebug("script=%s", escript.latin1());
+
+ QT_WA({
+ 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;
+ } , {
+ NEWTEXTMETRICA *tm = (NEWTEXTMETRICA *)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;
+ if (weight < 400)
+ styleKey.weight = QFont::Light;
+ else if (weight < 600)
+ styleKey.weight = QFont::Normal;
+ else if (weight < 700)
+ styleKey.weight = QFont::DemiBold;
+ else if (weight < 800)
+ styleKey.weight = QFont::Bold;
+ else
+ styleKey.weight = QFont::Black;
+
+ 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_OS_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<QFontDatabase::WritingSystem> systems = determineWritingSystemsFromTrueTypeBits(unicodeRange, codePageRange);
+ for (int i = 0; i < systems.count(); ++i)
+ family->writingSystems[systems.at(i)] = 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;
+ QT_WA({
+ familyName = QString::fromUtf16((ushort*)f->elfLogFont.lfFaceName);
+ },{
+ ENUMLOGFONTEXA *fa = (ENUMLOGFONTEXA *)f;
+ familyName = QString::fromLocal8Bit(fa->elfLogFont.lfFaceName);
+ });
+ QString script = QT_WA_INLINE(QString::fromUtf16((const ushort *)f->elfScript),
+ QString::fromLocal8Bit((const char *)((ENUMLOGFONTEXA *)f)->elfScript));
+
+ FONTSIGNATURE signature;
+ getFontSignature(familyName, textmetric, &signature);
+
+ // 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);
+
+ QT_WA({
+ LOGFONT lf;
+ lf.lfCharSet = DEFAULT_CHARSET;
+ if (fam.isNull()) {
+ lf.lfFaceName[0] = 0;
+ } else {
+ memcpy(lf.lfFaceName, fam.utf16(), sizeof(TCHAR)*qMin(fam.length()+1,32)); // 32 = Windows hard-coded
+ }
+ lf.lfPitchAndFamily = 0;
+
+ EnumFontFamiliesEx(dummy, &lf,
+ (FONTENUMPROC)storeFont, (LPARAM)privateDb(), 0);
+ } , {
+ LOGFONTA lf;
+ lf.lfCharSet = DEFAULT_CHARSET;
+ if (fam.isNull()) {
+ lf.lfFaceName[0] = 0;
+ } else {
+ QByteArray lname = fam.toLocal8Bit();
+ memcpy(lf.lfFaceName,lname.data(),
+ qMin(lname.length()+1,32)); // 32 = Windows hard-coded
+ }
+ lf.lfPitchAndFamily = 0;
+
+ EnumFontFamiliesExA(dummy, &lf,
+ (FONTENUMPROCA)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);
+ HFONT hfont;
+ QT_WA({
+ LOGFONTW lf;
+ memset(&lf, 0, sizeof(LOGFONTW));
+ memcpy(lf.lfFaceName, familyName.utf16(), qMin(LF_FACESIZE, familyName.size()));
+ lf.lfCharSet = DEFAULT_CHARSET;
+ hfont = CreateFontIndirectW(&lf);
+ } , {
+ LOGFONTA lf;
+ memset(&lf, 0, sizeof(LOGFONTA));
+ QByteArray lfam = familyName.toLocal8Bit();
+ memcpy(lf.lfFaceName, lfam.data(), qMin(LF_FACESIZE, lfam.length()));
+ lf.lfCharSet = DEFAULT_CHARSET;
+ hfont = CreateFontIndirectA(&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", family->name.latin1(), 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, const QFontPrivate *fp)
+{
+ fe->fontDef = request; // most settings are equal
+
+ HDC dc = ((request.styleStrategy & QFont::PreferDevice) && fp->hdc) ? fp->hdc : shared_dc();
+ SelectObject(dc, fe->hfont);
+ QT_WA({
+ TCHAR n[64];
+ GetTextFaceW(dc, 64, n);
+ fe->fontDef.family = QString::fromUtf16((ushort*)n);
+ fe->fontDef.fixedPitch = !(fe->tm.w.tmPitchAndFamily & TMPF_FIXED_PITCH);
+ } , {
+ char an[64];
+ GetTextFaceA(dc, 64, an);
+ fe->fontDef.family = QString::fromLocal8Bit(an);
+ fe->fontDef.fixedPitch = !(fe->tm.a.tmPitchAndFamily & TMPF_FIXED_PITCH);
+ });
+ if (fe->fontDef.pointSize < 0) {
+ fe->fontDef.pointSize = fe->fontDef.pixelSize * 72. / fp->dpi;
+ } else if (fe->fontDef.pixelSize == -1) {
+ fe->fontDef.pixelSize = qRound(fe->fontDef.pointSize * fp->dpi / 72.);
+ }
+}
+
+
+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;
+
+
+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 QFontPrivate *fp, const QFontDef &request, const QtFontDesc *desc,
+ const QStringList &family_list)
+{
+ LOGFONT lf;
+ memset(&lf, 0, sizeof(LOGFONT));
+
+ bool useDevice = (request.styleStrategy & QFont::PreferDevice) && fp->hdc;
+
+ HDC hdc = shared_dc();
+ QString font_name = desc->family->name;
+
+ if (useDevice) {
+ hdc = fp->hdc;
+ font_name = request.family;
+ }
+
+ bool stockFont = false;
+
+ HFONT hfont = 0;
+
+ if (fp->rawMode) { // will choose a stock font
+ int f, deffnt;
+ // ### why different?
+ if ((QSysInfo::WindowsVersion & QSysInfo::WV_NT_based) || QSysInfo::WindowsVersion == QSysInfo::WV_32s)
+ deffnt = SYSTEM_FONT;
+ else
+ deffnt = DEFAULT_GUI_FONT;
+ QString fam = desc->family->name.toLower();
+ if (fam == QLatin1String("default"))
+ f = deffnt;
+ else if (fam == QLatin1String("system"))
+ f = SYSTEM_FONT;
+#ifndef Q_OS_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 = -request.pixelSize;
+ lf.lfWidth = 0;
+ lf.lfEscapement = 0;
+ lf.lfOrientation = 0;
+ if (desc->style->key.weight == 50)
+ lf.lfWeight = FW_DONTCARE;
+ else
+ lf.lfWeight = (desc->style->key.weight*900)/99;
+ lf.lfItalic = (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_OS_WINCE
+ } else if (request.styleStrategy & QFont::PreferDevice) {
+ strat = OUT_DEVICE_PRECIS;
+ } else if (request.styleStrategy & QFont::PreferOutline) {
+ QT_WA({
+ strat = OUT_OUTLINE_PRECIS;
+ } , {
+ strat = OUT_TT_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_OS_WINCE
+ else if (request.styleStrategy & QFont::PreferQuality)
+ qual = PROOF_QUALITY;
+#endif
+
+ if (request.styleStrategy & QFont::PreferAntialias) {
+ if (QSysInfo::WindowsVersion >= QSysInfo::WV_XP)
+ qual = 5; // == CLEARTYPE_QUALITY;
+ 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");
+
+ QT_WA({
+ memcpy(lf.lfFaceName, fam.utf16(), sizeof(TCHAR)*qMin(fam.length()+1,32)); // 32 = Windows hard-coded
+ hfont = CreateFontIndirect(&lf);
+ } , {
+ // LOGFONTA and LOGFONTW are binary compatible
+ QByteArray lname = fam.toLocal8Bit();
+ memcpy(lf.lfFaceName,lname.data(),
+ qMin(lname.length()+1,32)); // 32 = Windows hard-coded
+ hfont = CreateFontIndirectA((LOGFONTA*)&lf);
+ });
+ if (!hfont)
+ qErrnoWarning("QFontEngine::loadEngine: CreateFontIndirect failed");
+
+ stockFont = (hfont == 0);
+ bool ttf = false;
+ int avWidth = 0;
+ BOOL res;
+ HGDIOBJ oldObj = SelectObject(hdc, hfont);
+ QT_WA({
+ TEXTMETRICW tm;
+ res = GetTextMetricsW(hdc, &tm);
+ avWidth = tm.tmAveCharWidth;
+ ttf = tm.tmPitchAndFamily & TMPF_TRUETYPE;
+ } , {
+ TEXTMETRICA tm;
+ res = GetTextMetricsA(hdc, &tm);
+ avWidth = tm.tmAveCharWidth;
+ ttf = tm.tmPitchAndFamily & TMPF_TRUETYPE;
+ });
+ SelectObject(hdc, oldObj);
+
+ if (hfont && (!ttf || request.stretch != 100)) {
+ DeleteObject(hfont);
+ if (!res)
+ qErrnoWarning("QFontEngine::loadEngine: GetTextMetrics failed");
+ lf.lfWidth = avWidth * request.stretch/100;
+ QT_WA({
+ hfont = CreateFontIndirect(&lf);
+ } , {
+ hfont = CreateFontIndirectA((LOGFONTA*)&lf);
+ });
+ if (!hfont)
+ qErrnoWarning("QFontEngine::loadEngine: CreateFontIndirect with stretch failed");
+ }
+
+#ifndef Q_OS_WINCE
+ if (hfont == 0) {
+ hfont = (HFONT)GetStockObject(ANSI_VAR_FONT);
+ stockFont = true;
+ }
+#else
+ if (hfont == 0) {
+ hfont = (HFONT)GetStockObject(SYSTEM_FONT);
+ stockFont = true;
+ }
+#endif
+
+ }
+ QFontEngineWin *few = new QFontEngineWin(font_name, hfont, stockFont, lf);
+
+ // 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;
+ }
+ }
+
+ QFontEngine *fe = few;
+ initFontInfo(few, request, fp);
+ if(script == QUnicodeTables::Common
+ && !(request.styleStrategy & QFont::NoFontMerging)
+ && !(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(few, list);
+ mfe->fontDef = fe->fontDef;
+ fe = mfe;
+ }
+ return fe;
+}
+
+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);
+
+ if(QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based && req.family.toLower() == QLatin1String("ms sans serif")) {
+ // small hack for Dos based machines to get the right font for non
+ // latin text when using the default font.
+ family_list << QLatin1String("Arial");
+ }
+
+ 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<int> 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, d, req, &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 = qMax(1, qRound(req.pointSize * d->dpi / 72.));
+ req.pointSize = 0;
+ 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
+ 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<quint32> getTrueTypeFontOffsets(const uchar *fontData)
+{
+ QList<quint32> offsets;
+ const quint32 headerTag = *reinterpret_cast<const quint32 *>(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<quint32>(fontData + 8);
+ for (uint i = 0; i < numFonts; ++i) {
+ offsets << qFromBigEndian<quint32>(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<quint16>(data + 4);
+ for (uint i = 0; i < numTables; ++i) {
+ const quint32 offset = 12 + 16 * i;
+ if (*reinterpret_cast<const quint32 *>(data + offset) == tag) {
+ *table = fileBegin + qFromBigEndian<quint32>(data + offset + 8);
+ *length = qFromBigEndian<quint32>(data + offset + 12);
+ return;
+ }
+ }
+ *table = 0;
+ *length = 0;
+ return;
+}
+
+static void getFamiliesAndSignatures(const QByteArray &fontData, QFontDatabasePrivate::ApplicationFont *appFont)
+{
+ const uchar *data = reinterpret_cast<const uchar *>(fontData.constData());
+
+ QList<quint32> 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<quint32>(table + 42);
+ signature.fsUsb[1] = qFromBigEndian<quint32>(table + 46);
+ signature.fsUsb[2] = qFromBigEndian<quint32>(table + 50);
+ signature.fsUsb[3] = qFromBigEndian<quint32>(table + 54);
+
+ signature.fsCsb[0] = qFromBigEndian<quint32>(table + 78);
+ signature.fsCsb[1] = qFromBigEndian<quint32>(table + 82);
+ }
+ appFont->signatures << signature;
+ }
+}
+
+static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt)
+{
+ if(!fnt->data.isEmpty()) {
+#ifndef Q_OS_WINCE
+ PtrAddFontMemResourceEx ptrAddFontMemResourceEx = (PtrAddFontMemResourceEx)QLibrary::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
+ TCHAR lpBuffer[MAX_PATH];
+ GetTempPath(MAX_PATH, lpBuffer);
+ QString s = QString::fromUtf16((const ushort *) 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
+ 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
+
+ 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
+ // supported from 2000 on, so no need to deal with the *A variant
+ PtrAddFontResourceExW ptrAddFontResourceExW = (PtrAddFontResourceExW)QLibrary::resolve(QLatin1String("gdi32"),
+ "AddFontResourceExW");
+ if (!ptrAddFontResourceExW)
+ return;
+
+ if (ptrAddFontResourceExW((LPCWSTR)fnt->fileName.utf16(), FR_PRIVATE, 0) == 0)
+ return;
+#endif
+
+ 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)QLibrary::resolve(QLatin1String("gdi32"),
+ "RemoveFontMemResourceEx");
+ if (!ptrRemoveFontMemResourceEx)
+ return false;
+
+ if (!ptrRemoveFontMemResourceEx(font.handle))
+ return false;
+#endif
+ } else {
+#ifdef Q_OS_WINCE
+ if (!RemoveFontResource((LPCWSTR)font.fileName.utf16()))
+ return false;
+#else
+ PtrRemoveFontResourceExW ptrRemoveFontResourceExW = (PtrRemoveFontResourceExW)QLibrary::resolve(QLatin1String("gdi32"),
+ "RemoveFontResourceExW");
+ if (!ptrRemoveFontResourceExW)
+ return false;
+
+ if (!ptrRemoveFontResourceExW((LPCWSTR)font.fileName.utf16(), FR_PRIVATE, 0))
+ return false;
+#endif
+ }
+
+ 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..15e626ea02
--- /dev/null
+++ b/src/gui/text/qfontdatabase_x11.cpp
@@ -0,0 +1,2064 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qplatformdefs.h>
+
+#include <qdatetime.h>
+#include <qdebug.h>
+#include <qpaintdevice.h>
+
+#include <private/qt_x11_p.h>
+#include "qx11info_x11.h"
+#include <qdebug.h>
+#include <qfile.h>
+#include <qtemporaryfile.h>
+#include <qabstractfileengine.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include <private/qfontengine_x11_p.h>
+
+#ifndef QT_NO_FONTCONFIG
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#if FC_VERSION >= 20402
+#include <fontconfig/fcfreetype.h>
+#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);
+
+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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // *-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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // 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 },
+ // *-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 },
+ // *-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 },
+ // 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 }
+
+};
+
+// ----- 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 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 != QString::fromLatin1("*") && (!desc || desc->family->count > 1))
+ fd->family +=
+ QString::fromLatin1(" [") + foundry + QString::fromLatin1("]");
+
+ 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;
+
+ FcChar8 *value = 0;
+ if (FcPatternGetString(pattern, FC_FAMILY, 0, &value) == FcResultMatch) {
+ fontDef.family = QString::fromUtf8(reinterpret_cast<const char *>(value));
+ }
+
+ double dpi;
+ if (FcPatternGetDouble(pattern, FC_DPI, 0, &dpi) != FcResultMatch) {
+ if (X11->display)
+ dpi = QX11Info::appDpiY();
+ else
+ dpi = 96; // ####
+ }
+
+ double size;
+ if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &size) == FcResultMatch)
+ fontDef.pixelSize = qRound(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
+};
+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
+};
+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
+};
+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
+};
+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
+};
+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;
+
+ 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)
+{
+ if (X11->has_fontconfig) {
+ initializeDb();
+ return;
+ }
+
+#ifdef QFONTDATABASE_DEBUG
+ QTime 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;
+
+ QTime 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", 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;
+ 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;
+ FcPatternAddInteger(pattern, FC_SLANT, slant_value);
+
+ double size_value = qMax(1, request.pixelSize);
+ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size_value);
+
+ int stretch = request.stretch;
+ if (!stretch)
+ stretch = 100;
+ FcPatternAddInteger(pattern, FC_WIDTH, stretch);
+
+ if (X11->display && QX11Info::appDepth(screen) <= 8) {
+ // can't do antialiasing on 8bpp
+ FcPatternAddBool(pattern, FC_ANTIALIAS, false);
+ } else if (request.styleStrategy & (QFont::PreferAntialias|QFont::NoAntialias)) {
+ FcPatternAddBool(pattern, FC_ANTIALIAS,
+ !(request.styleStrategy & QFont::NoAntialias));
+ }
+
+ if (script != QUnicodeTables::Common) {
+ Q_ASSERT(script < QUnicodeTables::ScriptCount);
+ FcLangSet *ls = FcLangSetCreate();
+ FcLangSetAdd(ls, (const FcChar8*)specialLanguages[script]);
+ 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 {
+ 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);
+ 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<int> encodings;
+ if (desc.encoding)
+ 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)
+ 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))
+ 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;
+}
+
+/*! \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 = qRound(qt_pixelSize(req.pointSize, d->dpi));
+ req.pointSize = 0;
+ 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
+ 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) {
+ 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-existant");
+ 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;
+
+ 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;
+ if (FcPatternGetString(pattern, FC_FAMILY, 0, &fam) == FcResultMatch) {
+ QString family = QString::fromUtf8(reinterpret_cast<const char *>(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
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp
new file mode 100644
index 0000000000..c4dffdf92b
--- /dev/null
+++ b/src/gui/text/qfontengine.cpp
@@ -0,0 +1,1623 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qdebug.h>
+#include <private/qfontengine_p.h>
+
+#include "qbitmap.h"
+#include "qpainter.h"
+#include "qpainterpath.h"
+#include "qvarlengtharray.h"
+#include <private/qpdf_p.h>
+#include <qmath.h>
+#include <qendian.h>
+#include <private/qharfbuzz_p.h>
+
+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();
+ }
+}
+
+
+
+QFontEngineGlyphCache::~QFontEngineGlyphCache()
+{
+}
+
+// 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<const QChar *>(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::ShaperFlag>(QTextEngine::DesignMetrics) : QFlags<QTextEngine::ShaperFlag>(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<const QChar *>(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()
+{
+ for (GlyphPointerHash::iterator it = m_glyphPointerHash.begin(), end = m_glyphPointerHash.end();
+ it != end; ++it) {
+ for (QList<QFontEngineGlyphCache*>::iterator it2 = it.value().begin(), end2 = it.value().end();
+ it2 != end2; ++it2)
+ delete *it2;
+ }
+ m_glyphPointerHash.clear();
+ for (GlyphIntHash::iterator it = m_glyphIntHash.begin(), end = m_glyphIntHash.end();
+ it != end; ++it) {
+ for (QList<QFontEngineGlyphCache*>::iterator it2 = it.value().begin(), end2 = it.value().end();
+ it2 != end2; ++it2)
+ delete *it2;
+ }
+ m_glyphIntHash.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<QFontEngine *>(this), hb_getSFntTable);
+ 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<QFontEngine *>(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<QFontEngine *>(this)->boundingBox(glyphs.glyphs[0]);
+ return bb.xoff;
+}
+
+
+void QFontEngine::getGlyphPositions(const QGlyphLayout &glyphs, const QTransform &matrix, QTextItem::RenderFlags flags,
+ QVarLengthArray<glyph_t> &glyphs_out, QVarLengthArray<QFixedPoint> &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 {
+ positions.resize(glyphs.numGlyphs);
+ glyphs_out.resize(glyphs.numGlyphs);
+ int i = 0;
+ 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());
+}
+
+
+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<QFixedPoint> positions;
+ QVarLengthArray<glyph_t> positioned_glyphs;
+ QTransform matrix;
+ matrix.translate(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();
+}
+
+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;
+ }
+ 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, const QTransform &t)
+{
+ QImage i = alphaMapForGlyph(glyph);
+ if (t.type() > QTransform::TxTranslate)
+ i = i.transformed(t);
+ Q_ASSERT(i.depth() <= 8); // To verify that transformed didn't change the format...
+ return i;
+}
+
+QImage QFontEngine::alphaRGBMapForGlyph(glyph_t glyph, int /* margin */, const QTransform &t)
+{
+ QImage alphaMask = alphaMapForGlyph(glyph, t);
+ QImage rgbMask(alphaMask.width(), alphaMask.height(), QImage::Format_RGB32);
+
+ for (int y=0; y<alphaMask.height(); ++y) {
+ uint *dst = (uint *) rgbMask.scanLine(y);
+ uchar *src = (uchar *) alphaMask.scanLine(y);
+ for (int x=0; x<alphaMask.width(); ++x)
+ dst[x] = qRgb(src[x], src[x], src[x]);
+ }
+
+ return rgbMask;
+}
+
+
+void QFontEngine::removeGlyphFromCache(glyph_t)
+{
+}
+
+QFontEngine::Properties QFontEngine::properties() const
+{
+ Properties p;
+#ifndef QT_NO_PRINTER
+ QByteArray psname = QPdf::stripSpecialCharacters(fontDef.family.toUtf8());
+#else
+ QByteArray psname = fontDef.family.toUtf8();
+#endif
+ psname += '-';
+ psname += QByteArray::number(fontDef.style);
+ psname += '-';
+ psname += QByteArray::number(fontDef.weight);
+
+ p.postscriptName = psname;
+ p.ascent = ascent();
+ p.descent = descent();
+ p.leading = leading();
+ p.emSquare = p.ascent;
+ p.boundingBox = QRectF(0, -p.ascent.toReal(), maxCharWidth(), (p.ascent + p.descent).toReal());
+ p.italicAngle = 0;
+ p.capHeight = p.ascent;
+ p.lineWidth = lineThickness();
+ return p;
+}
+
+void QFontEngine::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics)
+{
+ *metrics = boundingBox(glyph);
+ QFixedPoint p;
+ p.x = 0;
+ p.y = 0;
+ addGlyphsToPath(&glyph, &p, 1, path, QFlag(0));
+}
+
+QByteArray QFontEngine::getSfntTable(uint tag) const
+{
+ QByteArray table;
+ uint len = 0;
+ if (!getSfntTableData(tag, 0, &len))
+ return table;
+ if (!len)
+ return table;
+ table.resize(len);
+ if (!getSfntTableData(tag, reinterpret_cast<uchar *>(table.data()), &len))
+ return QByteArray();
+ return table;
+}
+
+void QFontEngine::expireGlyphCache()
+{
+ if (m_glyphCacheQueue.count() > 10) { // hold only 10 caches in memory.
+ QFontEngineGlyphCache *old = m_glyphCacheQueue.takeFirst();
+ // remove the value from either of our hashes
+ for (GlyphPointerHash::iterator i = m_glyphPointerHash.begin(); i != m_glyphPointerHash.end(); ++i) {
+ QList<QFontEngineGlyphCache *> list = i.value();
+ if (list.removeAll(old)) {
+ if (list.isEmpty())
+ m_glyphPointerHash.remove(i.key());
+ else
+ m_glyphPointerHash.insert(i.key(), list);
+ break;
+ }
+ }
+ for (GlyphIntHash::iterator i = m_glyphIntHash.begin(); i != m_glyphIntHash.end(); ++i) {
+ QList<QFontEngineGlyphCache *> list = i.value();
+ if (list.removeAll(old)) {
+ if (list.isEmpty())
+ m_glyphIntHash.remove(i.key());
+ else
+ m_glyphIntHash.insert(i.key(), list);
+ break;
+ }
+ }
+ delete old;
+ }
+}
+
+void QFontEngine::setGlyphCache(void *key, QFontEngineGlyphCache *data)
+{
+ Q_ASSERT(data);
+ QList<QFontEngineGlyphCache*> items = m_glyphPointerHash.value(key);
+
+ for (QList<QFontEngineGlyphCache*>::iterator it = items.begin(), end = items.end(); it != end; ++it) {
+ QFontEngineGlyphCache *c = *it;
+ if (qtransform_equals_no_translate(c->m_transform, data->m_transform)) {
+ if (c == data)
+ return;
+ items.removeAll(c);
+ delete c;
+ break;
+ }
+ }
+ items.append(data);
+ m_glyphPointerHash.insert(key, items);
+
+ m_glyphCacheQueue.append(data);
+ expireGlyphCache();
+}
+
+void QFontEngine::setGlyphCache(QFontEngineGlyphCache::Type key, QFontEngineGlyphCache *data)
+{
+ Q_ASSERT(data);
+ QList<QFontEngineGlyphCache*> items = m_glyphIntHash.value(key);
+
+ for (QList<QFontEngineGlyphCache*>::iterator it = items.begin(), end = items.end(); it != end; ++it) {
+ QFontEngineGlyphCache *c = *it;
+ if (qtransform_equals_no_translate(c->m_transform, data->m_transform)) {
+ if (c == data)
+ return;
+ items.removeAll(c);
+ delete c;
+ break;
+ }
+ }
+ items.append(data);
+ m_glyphIntHash.insert(key, items);
+
+ m_glyphCacheQueue.append(data);
+ expireGlyphCache();
+}
+
+QFontEngineGlyphCache *QFontEngine::glyphCache(void *key, const QTransform &transform) const
+{
+ QList<QFontEngineGlyphCache*> items = m_glyphPointerHash.value(key);
+
+ for (QList<QFontEngineGlyphCache*>::iterator it = items.begin(), end = items.end(); it != end; ++it) {
+ QFontEngineGlyphCache *c = *it;
+ if (qtransform_equals_no_translate(c->m_transform, transform)) {
+ m_glyphCacheQueue.removeAll(c); // last used, move it up
+ m_glyphCacheQueue.append(c);
+ return c;
+ }
+ }
+ return 0;
+}
+
+QFontEngineGlyphCache *QFontEngine::glyphCache(QFontEngineGlyphCache::Type key, const QTransform &transform) const
+{
+ QList<QFontEngineGlyphCache*> items = m_glyphIntHash.value(key);
+
+ for (QList<QFontEngineGlyphCache*>::iterator it = items.begin(), end = items.end(); it != end; ++it) {
+ QFontEngineGlyphCache *c = *it;
+ if (qtransform_equals_no_translate(c->m_transform, transform)) {
+ m_glyphCacheQueue.removeAll(c); // last used, move it up
+ m_glyphCacheQueue.append(c);
+ return c;
+ }
+ }
+ return 0;
+}
+
+#if defined(Q_WS_WIN) || defined(Q_WS_X11) || defined(Q_WS_QWS)
+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<const uchar *>(tab.constData());
+
+ unsigned short version = qFromBigEndian<quint16>(table);
+ if (version != 0) {
+// qDebug("wrong version");
+ return;
+ }
+
+ unsigned short numTables = qFromBigEndian<quint16>(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<quint16>(header);
+ ushort length = qFromBigEndian<quint16>(header+2);
+ ushort coverage = qFromBigEndian<quint16>(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<quint16>(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<quint16>(data+off)) << 16) + qFromBigEndian<quint16>(data+off+2);
+ p.adjust = QFixed(((int)(short)qFromBigEndian<quint16>(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<quint16>(reinterpret_cast<const uchar *>(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<quint16>(header) != 0)
+ return 0;
+
+ unsigned short numTables = qFromBigEndian<quint16>(header + 2);
+ const uchar *maps = table + 4;
+ if (maps + 8 * numTables > endPtr)
+ return 0;
+
+ int tableToUse = -1;
+ int score = 0;
+ for (int n = 0; n < numTables; ++n) {
+ const quint16 platformId = qFromBigEndian<quint16>(maps + 8 * n);
+ const quint16 platformSpecificId = qFromBigEndian<quint16>(maps + 8 * n + 2);
+ switch (platformId) {
+ case 0: // Unicode
+ if (score < 4 &&
+ (platformSpecificId == 0 ||
+ platformSpecificId == 2 ||
+ platformSpecificId == 3)) {
+ tableToUse = n;
+ score = 4;
+ } else if (score < 3 && platformSpecificId == 1) {
+ tableToUse = n;
+ score = 3;
+ }
+ break;
+ case 1: // Apple
+ if (score < 2 && platformSpecificId == 0) { // Apple Roman
+ tableToUse = n;
+ score = 2;
+ }
+ break;
+ case 3: // Microsoft
+ switch (platformSpecificId) {
+ case 0:
+ if (score < 1) {
+ tableToUse = n;
+ score = 1;
+ }
+ break;
+ case 1:
+ if (score < 5) {
+ tableToUse = n;
+ score = 5;
+ }
+ break;
+ case 0xa:
+ if (score < 6) {
+ tableToUse = n;
+ score = 6;
+ }
+ break;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ if(tableToUse < 0)
+ return 0;
+ *isSymbolFont = (score == 1);
+
+ unsigned int unicode_table = qFromBigEndian<quint32>(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<quint16>(header);
+ unsigned int length;
+ if(format < 8)
+ length = qFromBigEndian<quint16>(header + 2);
+ else
+ length = qFromBigEndian<quint32>(header + 4);
+
+ if (table + unicode_table + length > endPtr)
+ return 0;
+ *cmapSize = length;
+ return table + unicode_table;
+}
+
+quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, uint unicode)
+{
+ unsigned short format = qFromBigEndian<quint16>(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<quint16>(cmap + 6);
+ const unsigned char *ends = cmap + 14;
+ quint16 endIndex = 0;
+ int i = 0;
+ for (; i < segCountX2/2 && (endIndex = qFromBigEndian<quint16>(ends + 2*i)) < unicode; i++) {}
+
+ const unsigned char *idx = ends + segCountX2 + 2 + 2*i;
+ quint16 startIndex = qFromBigEndian<quint16>(idx);
+
+ if (startIndex > unicode)
+ return 0;
+
+ idx += segCountX2;
+ qint16 idDelta = (qint16)qFromBigEndian<quint16>(idx);
+ idx += segCountX2;
+ quint16 idRangeoffset_t = (quint16)qFromBigEndian<quint16>(idx);
+
+ quint16 glyphIndex;
+ if (idRangeoffset_t) {
+ quint16 id = qFromBigEndian<quint16>(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<quint16>(cmap + 2);
+
+ quint16 firstCode6 = qFromBigEndian<quint16>(cmap + 6);
+ if (unicode < firstCode6)
+ return 0;
+
+ quint16 entryCount6 = qFromBigEndian<quint16>(cmap + 8);
+ if (entryCount6 * 2 + 10 > tableSize)
+ return 0;
+
+ quint16 sentinel6 = firstCode6 + entryCount6;
+ if (unicode >= sentinel6)
+ return 0;
+
+ quint16 entryIndex6 = unicode - firstCode6;
+ return qFromBigEndian<quint16>(cmap + 10 + (entryIndex6 * 2));
+ } else if (format == 12) {
+ quint32 nGroups = qFromBigEndian<quint32>(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<quint32>(cmap + 12*middle);
+ if(unicode < startCharCode)
+ right = middle - 1;
+ else {
+ quint32 endCharCode = qFromBigEndian<quint32>(cmap + 12*middle + 4);
+ if(unicode <= endCharCode)
+ return qFromBigEndian<quint32>(cmap + 12*middle + 8) + unicode - startCharCode;
+ left = middle + 1;
+ }
+ }
+ } else {
+ qDebug("cmap table of format %d not implemented", format);
+ }
+
+ 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<QFixedPoint> positions;
+ QVarLengthArray<glyph_t> positioned_glyphs;
+ QTransform matrix;
+ matrix.translate(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)
+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<QFixedPoint> positions;
+ QVarLengthArray<glyph_t> glyphs;
+ QTransform matrix;
+ matrix.translate(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<QRgb> 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) {
+
+ QGlyphLayoutInstance tmp = glyphs->instance(glyph_pos);
+ for (int x = 1; x < engines.size(); ++x) {
+ QFontEngine *engine = engines.at(x);
+ if (!engine) {
+ const_cast<QFontEngineMulti *>(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::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;
+}
+
+QFontEngine *QFontEngineMulti::engine(int at) const
+{
+ Q_ASSERT(at < engines.size());
+ return engines.at(at);
+}
+
+QImage QFontEngineMulti::alphaMapForGlyph(glyph_t)
+{
+ Q_ASSERT(false);
+ return QImage();
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/gui/text/qfontengine_ft.cpp b/src/gui/text/qfontengine_ft.cpp
new file mode 100644
index 0000000000..45a25a47b9
--- /dev/null
+++ b/src/gui/text/qfontengine_ft.cpp
@@ -0,0 +1,1904 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <qmath.h>
+#include <private/qpdf_p.h>
+#include <private/qharfbuzz_p.h>
+
+#include <private/qpdf_p.h>
+
+#include "qfontengine_ft_p.h"
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_OUTLINE_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
+
+#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<QFontEngine::FaceId, QFreetypeFace *> faces;
+};
+
+#ifdef QT_NO_THREAD
+Q_GLOBAL_STATIC(QtFreetypeData, theFreetypeData)
+
+QtFreetypeData *qt_getFreetypeData()
+{
+ return theFreetypeData();
+}
+#else
+Q_GLOBAL_STATIC(QThreadStorage<QtFreetypeData *>, 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)
+{
+ int load_flags = (flags & HB_ShaperFlag_UseDesignMetrics) ? FT_LOAD_NO_HINTING : FT_LOAD_DEFAULT;
+
+ if (HB_Error error = (HB_Error)FT_Load_Glyph(face, glyph, load_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.
+ */
+QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id)
+{
+ if (face_id.filename.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 = new QFreetypeFace;
+ FT_Face face;
+ 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;
+ freetype->fontData = qt_fontdata_from_index(idx.toInt(&ok));
+ if (!ok)
+ freetype->fontData = QByteArray();
+ } else if (!(file.fileEngine()->fileFlags(QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::LocalDiskFlag)) {
+ if (!file.open(QIODevice::ReadOnly)) {
+ delete freetype;
+ return 0;
+ }
+ freetype->fontData = file.readAll();
+ }
+ if (!freetype->fontData.isEmpty()) {
+ if (FT_New_Memory_Face(freetypeData->library, (const FT_Byte *)freetype->fontData.constData(), freetype->fontData.size(), face_id.index, &face)) {
+ delete freetype;
+ return 0;
+ }
+ } else if (FT_New_Face(freetypeData->library, face_id.filename, face_id.index, &face)) {
+ delete freetype;
+ return 0;
+ }
+ freetype->face = face;
+
+ freetype->hbFace = qHBNewFace(face, hb_getSFntTable);
+ freetype->ref = 0;
+ freetype->xsize = 0;
+ freetype->ysize = 0;
+ freetype->matrix.xx = 0x10000;
+ freetype->matrix.yy = 0x10000;
+ freetype->matrix.xy = 0;
+ freetype->matrix.yx = 0;
+ freetype->unicode_map = 0;
+ freetype->symbol_map = 0;
+#ifndef QT_NO_FONTCONFIG
+ freetype->charset = 0;
+#endif
+
+ memset(freetype->cmapCache, 0, sizeof(freetype->cmapCache));
+
+ for (int i = 0; i < freetype->face->num_charmaps; ++i) {
+ FT_CharMap cm = freetype->face->charmaps[i];
+ switch(cm->encoding) {
+ case FT_ENCODING_UNICODE:
+ freetype->unicode_map = cm;
+ break;
+ case FT_ENCODING_APPLE_ROMAN:
+ case FT_ENCODING_ADOBE_LATIN_1:
+ if (!freetype->unicode_map || freetype->unicode_map->encoding != FT_ENCODING_UNICODE)
+ freetype->unicode_map = cm;
+ break;
+ case FT_ENCODING_ADOBE_CUSTOM:
+ case FT_ENCODING_MS_SYMBOL:
+ if (!freetype->symbol_map)
+ freetype->symbol_map = cm;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!FT_IS_SCALABLE(freetype->face) && freetype->face->num_fixed_sizes == 1)
+ FT_Set_Char_Size (face, X_SIZE(freetype->face, 0), Y_SIZE(freetype->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,
+ freetype->face->charmap ? freetype->face->charmap->encoding : 0,
+ freetype->unicode_map ? freetype->unicode_map->encoding : 0,
+ freetype->symbol_map ? freetype->symbol_map->encoding : 0);
+
+ for (int i = 0; i < 256; i += 8)
+ qDebug(" %x: %d %d %d %d %d %d %d %d", i,
+ FcCharSetHasChar(freetype->charset, i), FcCharSetHasChar(freetype->charset, i),
+ FcCharSetHasChar(freetype->charset, i), FcCharSetHasChar(freetype->charset, i),
+ FcCharSetHasChar(freetype->charset, i), FcCharSetHasChar(freetype->charset, i),
+ FcCharSetHasChar(freetype->charset, i), FcCharSetHasChar(freetype->charset, i));
+#endif
+
+ FT_Set_Charmap(freetype->face, freetype->unicode_map);
+ freetypeData->faces.insert(face_id, freetype);
+ }
+ freetype->ref.ref();
+ 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
+ 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 = fontDef.pixelSize << 6;
+ *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.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;
+ antialias = true;
+ default_load_flags = 0;
+ default_hint_style = HintNone;
+ subpixelType = Subpixel_None;
+ lcdFilterType = 0;
+#if defined(FT_LCD_FILTER_H)
+ lcdFilterType = (int) 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)
+{
+ defaultFormat = format;
+ this->antialias = antialias;
+ if (!antialias)
+ glyphFormat = QFontEngineGlyphCache::Raster_Mono;
+ face_id = faceId;
+ freetype = QFreetypeFace::getFace(face_id);
+ if (!freetype) {
+ xsize = 0;
+ ysize = 0;
+ return false;
+ }
+
+ 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();
+
+ //underline metrics
+ if (FT_IS_SCALABLE(face)) {
+ 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));
+ 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;
+ } 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)
+ /*
+ 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;
+}
+
+QFontEngineFT::Glyph *QFontEngineFT::loadGlyphMetrics(QGlyphSet *set, uint glyph) const
+{
+ Glyph *g = set->glyph_data.value(glyph);
+ if (g)
+ return g;
+
+ int load_flags = FT_LOAD_DEFAULT | default_load_flags;
+ if (set->outline_drawing)
+ load_flags = FT_LOAD_NO_BITMAP;
+
+ // apply our matrix to this, but note that the metrics will not be affected by this.
+ FT_Matrix matrix = freetype->matrix;
+ FT_Face face = lockFace();
+ 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;
+ 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, 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->glyph_data.value(glyph);
+ if (g && g->format == format) {
+ if (uploadToServer && !g->uploadedToServer) {
+ set->glyph_data[glyph] = 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 = FT_LOAD_DEFAULT | default_load_flags;
+
+ int load_target = default_hint_style == HintLight
+ ? FT_LOAD_TARGET_LIGHT
+ : FT_LOAD_TARGET_NORMAL;
+
+ if (set->outline_drawing)
+ load_flags |= FT_LOAD_NO_BITMAP;
+
+ 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 (default_hint_style == HintNone)
+ load_flags |= FT_LOAD_NO_HINTING;
+ else
+ load_flags |= load_target;
+
+#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_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;
+ 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);
+ 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(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->glyph_data[glyph] = 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 = fontDef.family.toUtf8();
+#ifndef QT_NO_PRINTER
+ p.postscriptName = QPdf::stripSpecialCharacters(p.postscriptName);
+#endif
+ }
+
+ 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.stretch != 100 && FT_IS_SCALABLE(freetype->face))
+ s |= SynthesizedStretch;
+ return s;
+}
+
+QFixed QFontEngineFT::ascent() const
+{
+ return QFixed::fromFixed(metrics.ascender);
+}
+
+QFixed QFontEngineFT::descent() const
+{
+ return QFixed::fromFixed(-metrics.descender);
+}
+
+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<char_table_entries> 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<QFontEngineFT *>(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<QFontEngineFT *>(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(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];
+
+ qDeleteAll(gs->glyph_data);
+ gs->glyph_data.clear();
+
+ gs->id = allocateServerGlyphSet();
+
+ gs->transformationMatrix = m;
+ gs->outline_drawing = draw_as_outline;
+ }
+
+ return gs;
+}
+
+bool QFontEngineFT::loadGlyphs(QGlyphSet *gs, glyph_t *glyphs, int num_glyphs, GlyphFormat format)
+{
+ FT_Face face = 0;
+
+ for (int i = 0; i < num_glyphs; ++i) {
+ if (!gs->glyph_data.contains(glyphs[i])
+ || gs->glyph_data.value(glyphs[i])->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], 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<QFixedPoint> positions;
+ QVarLengthArray<glyph_t> 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;
+ }
+
+ 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);
+ if (mirrored)
+ uc = QChar::mirroredChar(uc);
+ 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 (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)
+ && 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 (flags & QTextEngine::GlyphIndicesOnly)
+ return true;
+
+ recalcAdvances(glyphs, flags);
+
+ return true;
+}
+
+void QFontEngineFT::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const
+{
+ FT_Face face = 0;
+ if (flags & QTextEngine::DesignMetrics) {
+ for (int i = 0; i < glyphs->numGlyphs; i++) {
+ Glyph *g = defaultGlyphSet.glyph_data.value(glyphs->glyphs[i]);
+ if (g) {
+ glyphs->advances_x[i] = QFixed::fromFixed(g->linearAdvance);
+ } else {
+ if (!face)
+ face = lockFace();
+ g = loadGlyph(glyphs->glyphs[i], Format_None, true);
+ glyphs->advances_x[i] = QFixed::fromFixed(face->glyph->linearHoriAdvance >> 10);
+ }
+ glyphs->advances_y[i] = 0;
+ }
+ } else {
+ for (int i = 0; i < glyphs->numGlyphs; i++) {
+ Glyph *g = defaultGlyphSet.glyph_data.value(glyphs->glyphs[i]);
+ if (g) {
+ glyphs->advances_x[i] = QFixed(g->advance);
+ } else {
+ if (!face)
+ face = lockFace();
+ g = loadGlyph(glyphs->glyphs[i], Format_None, true);
+ glyphs->advances_x[i] = QFixed::fromFixed(face->glyph->metrics.horiAdvance).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.glyph_data.value(glyphs.glyphs[i]);
+ if (!g) {
+ if (!face)
+ face = lockFace();
+ g = loadGlyph(glyphs.glyphs[i], 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.glyph_data.value(glyph);
+ if (!g) {
+ face = lockFace();
+ g = loadGlyph(glyph, 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;
+ } 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)
+{
+ 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];
+ qDeleteAll(glyphSet->glyph_data);
+ glyphSet->glyph_data.clear();
+ glyphSet->id = allocateServerGlyphSet();
+ glyphSet->transformationMatrix = m;
+ }
+ Q_ASSERT(glyphSet);
+ } else {
+ glyphSet = &defaultGlyphSet;
+ }
+ Glyph * g = glyphSet->glyph_data.value(glyph);
+ if (!g) {
+ face = lockFace();
+ g = loadGlyphMetrics(glyphSet, glyph);
+ }
+
+ 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)
+{
+ lockFace();
+
+ GlyphFormat glyph_format = antialias ? Format_A8 : Format_Mono;
+
+ Glyph *glyph = loadGlyph(g, glyph_format);
+ if (!glyph)
+ return QImage();
+
+ 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<QRgb> colors(256);
+ for (int i=0; i<256; ++i)
+ colors[i] = qRgba(0, 0, 0, i);
+ img.setColorTable(colors);
+ } else {
+ QVector<QRgb> 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;
+}
+
+void QFontEngineFT::removeGlyphFromCache(glyph_t glyph)
+{
+ delete defaultGlyphSet.glyph_data.take(glyph);
+}
+
+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;
+}
+
+QFontEngineFT::QGlyphSet::~QGlyphSet()
+{
+ qDeleteAll(glyph_data);
+}
+
+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();
+ HB_Error result = freetype->getPointInOutline(glyph, 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..284904bee1
--- /dev/null
+++ b/src/gui/text/qfontengine_ft_p.h
@@ -0,0 +1,323 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <ft2build.h>
+#include FT_FREETYPE_H
+
+#if defined(Q_WS_X11)
+#include <private/qt_x11_p.h>
+#endif
+
+#include <unistd.h>
+
+#ifndef QT_NO_FONTCONFIG
+#include <fontconfig/fontconfig.h>
+#endif
+
+#include <qmutex.h>
+
+#include <harfbuzz-shaper.h>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ * 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);
+ 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:
+ QFreetypeFace() : _lock(QMutex::Recursive) {}
+ ~QFreetypeFace() {}
+ QAtomicInt ref;
+ QMutex _lock;
+ QByteArray fontData;
+};
+
+class Q_GUI_EXPORT QFontEngineFT : public QFontEngine
+{
+public:
+ enum GlyphFormat {
+ Format_None,
+ Format_Render = Format_None,
+ Format_Mono,
+ Format_A8,
+ Format_A32
+ };
+
+ /* 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 QGlyphSet
+ {
+ QGlyphSet();
+ ~QGlyphSet();
+ FT_Matrix transformationMatrix;
+ unsigned long id; // server sided id, GlyphSet for X11
+ bool outline_drawing;
+ mutable QHash<int, Glyph *> glyph_data; // maps from glyph index to glyph data
+ };
+
+ virtual QFontEngine::FaceId faceId() const;
+ virtual QFontEngine::Properties properties() const;
+ virtual QFixed emSquareSize() const;
+
+ 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);
+ 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, GlyphFormat format = Format_None, bool fetchMetricsOnly = false) const
+ { return loadGlyph(&defaultGlyphSet, glyph, format, fetchMetricsOnly); }
+ Glyph *loadGlyph(QGlyphSet *set, uint glyph, 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.glyph_data.value(g); }
+
+ QGlyphSet *loadTransformedGlyphSet(const QTransform &matrix);
+ bool loadGlyphs(QGlyphSet *gs, glyph_t *glyphs, int num_glyphs, GlyphFormat format = Format_Render);
+
+#if defined(Q_WS_QWS)
+ 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);
+
+ virtual HB_Error getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints);
+
+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;
+
+ enum HintStyle {
+ HintNone,
+ HintLight,
+ HintMedium,
+ HintFull
+ };
+
+ HintStyle default_hint_style;
+
+ bool antialias;
+ bool transform;
+ SubpixelAntialiasingType subpixelType;
+ int lcdFilterType;
+ bool canUploadGlyphsToServer;
+ bool embeddedbitmap;
+
+private:
+ QFontEngineFT::Glyph *loadGlyphMetrics(QGlyphSet *set, uint glyph) const;
+
+ GlyphFormat defaultFormat;
+ FT_Matrix matrix;
+
+ QList<QGlyphSet> 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;
+};
+
+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..40d145a13c
--- /dev/null
+++ b/src/gui/text/qfontengine_mac.mm
@@ -0,0 +1,1701 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <private/qapplication_p.h>
+#include <private/qfontengine_p.h>
+#include <private/qpainter_p.h>
+#include <private/qtextengine_p.h>
+#include <qbitmap.h>
+#include <private/qpaintengine_mac_p.h>
+#include <private/qprintengine_mac_p.h>
+#include <private/qpdf_p.h>
+#include <qglobal.h>
+#include <qpixmap.h>
+#include <qpixmapcache.h>
+#include <qvarlengtharray.h>
+#include <qdebug.h>
+#include <qendian.h>
+
+#include <ApplicationServices/ApplicationServices.h>
+#include <AppKit/AppKit.h>
+
+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<QMacFontPath*>(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<QMacFontPath*>(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<QMacFontPath*>(data);
+ p->path->moveTo(p->x + pt->x, p->y + pt->y);
+ return noErr;
+}
+
+OSStatus QMacFontPath::closePath(void *data)
+{
+ static_cast<QMacFontPath*>(data)->path->closeSubpath();
+ return noErr;
+}
+
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+QCoreTextFontEngineMulti::QCoreTextFontEngineMulti(const ATSFontFamilyRef &, const ATSFontRef &atsFontRef, const QFontDef &fontDef, bool)
+ : 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;
+ }
+
+ QCFString name;
+ ATSFontGetName(atsFontRef, kATSOptionFlagsDefault, &name);
+ QCFType<CTFontDescriptorRef> descriptor = CTFontDescriptorCreateWithNameAndSize(name, fontDef.pixelSize);
+ QCFType<CTFontRef> baseFont = CTFontCreateWithFontDescriptor(descriptor, fontDef.pixelSize, 0);
+ ctfont = CTFontCreateCopyWithSymbolicTraits(baseFont, fontDef.pixelSize, 0, 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);
+ }
+
+ const void *keys[] = { NSFontAttributeName };
+ const void *values[] = { ctfont };
+ attributeDict = CFDictionaryCreate(0, keys, values, 1,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ QCoreTextFontEngine *fe = new QCoreTextFontEngine(ctfont, fontDef, this);
+ fe->ref.ref();
+ engines.append(fe);
+
+}
+
+QCoreTextFontEngineMulti::~QCoreTextFontEngineMulti()
+{
+ CFRelease(ctfont);
+}
+
+uint QCoreTextFontEngineMulti::fontIndexForFont(CTFontRef id) const
+{
+ for (int i = 0; i < engines.count(); ++i) {
+ if (CFEqual(engineAt(i)->ctfont, id))
+ return i;
+ }
+
+ QCoreTextFontEngineMulti *that = const_cast<QCoreTextFontEngineMulti *>(this);
+ QCoreTextFontEngine *fe = new QCoreTextFontEngine(id, fontDef, that);
+ 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,
+ unsigned short *logClusters, const HB_CharAttributes *) const
+{
+ QCFType<CFStringRef> cfstring = CFStringCreateWithCharactersNoCopy(0,
+ reinterpret_cast<const UniChar *>(str),
+ len, kCFAllocatorNull);
+ QCFType<CFAttributedStringRef> attributedString = CFAttributedStringCreate(0, cfstring, attributeDict);
+ QCFType<CTTypesetterRef> typeSetter = CTTypesetterCreateWithAttributedString(attributedString);
+ CFRange range = {0, 0};
+ QCFType<CTLineRef> 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)
+ return false;
+
+ const bool rtl = (CTRunGetStatus(static_cast<CTRunRef>(CFArrayGetValueAtIndex(array, 0))) & kCTRunStatusRightToLeft);
+
+ bool outOBounds = false;
+ for (uint i = 0; i < arraySize; ++i) {
+ CTRunRef run = static_cast<CTRunRef>(CFArrayGetValueAtIndex(array, rtl ? (arraySize - 1 - i) : i));
+ CFIndex glyphCount = CTRunGetGlyphCount(run);
+ if (glyphCount == 0)
+ continue;
+
+ Q_ASSERT((CTRunGetStatus(run) & kCTRunStatusRightToLeft) == rtl);
+
+ 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<CTFontRef>(CFDictionaryGetValue(runAttribs, NSFontAttributeName));
+ const uint fontIndex = (fontIndexForFont(runFont) << 24);
+ //NSLog(@"Run Font Name = %@", CTFontCopyFamilyName(runFont));
+ QVarLengthArray<CGGlyph, 512> cgglyphs(0);
+ const CGGlyph *tmpGlyphs = CTRunGetGlyphsPtr(run);
+ if (!tmpGlyphs) {
+ cgglyphs.resize(glyphCount);
+ CTRunGetGlyphs(run, range, cgglyphs.data());
+ tmpGlyphs = cgglyphs.constData();
+ }
+ QVarLengthArray<CGPoint, 512> 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<CFIndex, 512> 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);
+ outAdvances_y[idx] = QFixed::fromReal(tmpPoints[i + 1].y - tmpPoints[i].y);
+ }
+ double runWidth = ceil(CTRunGetTypographicBounds(run, range, 0, 0, 0));
+ runWidth += tmpPoints[0].x;
+ outGlyphs[rtl ? 0 : (glyphCount - 1)] = tmpGlyphs[glyphCount - 1] | fontIndex;
+ outAdvances_x[rtl ? 0 : (glyphCount - 1)] = QFixed::fromReal(runWidth - tmpPoints[glyphCount - 1].x);
+ }
+ 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
+{
+ return stringToCMap(str, len, glyphs, nglyphs, flags, 0, 0);
+}
+
+void QCoreTextFontEngineMulti::recalcAdvances(int , QGlyphLayout *, QTextEngine::ShaperFlags) const
+{
+}
+void QCoreTextFontEngineMulti::doKerning(int , QGlyphLayout *, QTextEngine::ShaperFlags) const
+{
+}
+
+void QCoreTextFontEngineMulti::loadEngine(int)
+{
+ // Do nothing
+ Q_ASSERT(false);
+}
+
+
+
+QCoreTextFontEngine::QCoreTextFontEngine(CTFontRef font, const QFontDef &def,
+ QCoreTextFontEngineMulti *multiEngine)
+{
+ fontDef = def;
+ parentEngine = multiEngine;
+ synthesisFlags = 0;
+ ctfont = font;
+ CFRetain(ctfont);
+ ATSFontRef atsfont = CTFontGetPlatformFont(ctfont, 0);
+ cgFont = CGFontCreateWithPlatformFont(&atsfont);
+ CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ctfont);
+ if (fontDef.weight >= QFont::Bold && !(traits & kCTFontBoldTrait)) {
+ synthesisFlags |= SynthesizedBold;
+ }
+
+ if (fontDef.style != QFont::StyleNormal && !(traits & kCTFontItalicTrait)) {
+ synthesisFlags |= SynthesizedItalic;
+ }
+
+ QByteArray os2Table = getSfntTable(MAKE_TAG('O', 'S', '/', '2'));
+ if (os2Table.size() >= 10)
+ fsType = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(os2Table.constData() + 8));
+}
+
+QCoreTextFontEngine::~QCoreTextFontEngine()
+{
+ CFRelease(ctfont);
+ CFRelease(cgFont);
+}
+
+bool QCoreTextFontEngine::stringToCMap(const QChar *, int, QGlyphLayout *, int *, QTextEngine::ShaperFlags) const
+{
+ return false;
+}
+
+glyph_metrics_t QCoreTextFontEngine::boundingBox(const QGlyphLayout &glyphs)
+{
+ QFixed w;
+ 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 QCoreTextFontEngine::boundingBox(glyph_t glyph)
+{
+ glyph_metrics_t ret;
+ CGGlyph g = glyph;
+ CGRect rect = CTFontGetBoundingRectsForGlyphs(ctfont, kCTFontHorizontalOrientation, &g, 0, 1);
+ 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);
+ return ret;
+}
+
+QFixed QCoreTextFontEngine::ascent() const
+{
+ return QFixed::fromReal(CTFontGetAscent(ctfont));
+}
+QFixed QCoreTextFontEngine::descent() const
+{
+ return QFixed::fromReal(CTFontGetDescent(ctfont));
+}
+QFixed QCoreTextFontEngine::leading() const
+{
+ return QFixed::fromReal(CTFontGetLeading(ctfont));
+}
+QFixed QCoreTextFontEngine::xHeight() const
+{
+ return QFixed::fromReal(CTFontGetXHeight(ctfont));
+}
+QFixed QCoreTextFontEngine::averageCharWidth() const
+{
+ // ### Need to implement properly and get the information from the OS/2 Table.
+ return QFontEngine::averageCharWidth();
+}
+
+qreal QCoreTextFontEngine::maxCharWidth() const
+{
+ // ### Max Help!
+ return 0;
+
+}
+qreal QCoreTextFontEngine::minLeftBearing() const
+{
+ // ### Min Help!
+ return 0;
+
+}
+qreal QCoreTextFontEngine::minRightBearing() const
+{
+ // ### Max Help! (even thought it's right)
+ return 0;
+
+}
+
+void QCoreTextFontEngine::draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight)
+{
+ QVarLengthArray<QFixedPoint> positions;
+ QVarLengthArray<glyph_t> 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<CGSize> advances(glyphs.size());
+ QVarLengthArray<CGGlyph> 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<ConvertPathInfo *>(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, tanf(14 * acosf(0) / 90), 1, 0, 0));
+
+
+ for (int i = 0; i < nGlyphs; ++i) {
+ QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, glyphs[i], &cgMatrix);
+ ConvertPathInfo info(path, positions[i].toPointF());
+ CGPathApply(cgpath, &info, convertCGPathToQPainterPath);
+ }
+}
+
+QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph)
+{
+ 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 = QCoreGraphicsPaintEngine::macGenericColorSpace();
+#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
+ uint cgflags = kCGImageAlphaNoneSkipFirst;
+#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version
+ if(QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4)
+ cgflags |= kCGBitmapByteOrder32Host;
+#endif
+#else
+ CGImageAlphaInfo cgflags = kCGImageAlphaNoneSkipFirst;
+#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));
+ 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);
+
+ ATSFontRef atsfont = CTFontGetPlatformFont(ctfont, 0);
+ QCFType<CGFontRef> cgFont = CGFontCreateWithPlatformFont(&atsfont);
+ CGContextSetFont(ctx, cgFont);
+
+ qreal pos_x = -br.x.toReal()+1, pos_y = im.height()+br.y.toReal();
+ 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);
+
+ QImage indexed(im.width(), im.height(), QImage::Format_Indexed8);
+ QVector<QRgb> colors(256);
+ for (int i=0; i<256; ++i)
+ colors[i] = qRgba(0, 0, 0, i);
+ indexed.setColorTable(colors);
+
+ for (int y=0; y<im.height(); ++y) {
+ uint *src = (uint*) im.scanLine(y);
+ uchar *dst = indexed.scanLine(y);
+ for (int x=0; x<im.width(); ++x) {
+ *dst = qGray(*src);
+ ++dst;
+ ++src;
+ }
+ }
+
+ return indexed;
+}
+
+void QCoreTextFontEngine::recalcAdvances(int numGlyphs, QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const
+{
+ Q_ASSERT(false);
+ Q_UNUSED(numGlyphs);
+ Q_UNUSED(glyphs);
+ Q_UNUSED(flags);
+}
+
+QFontEngine::FaceId QCoreTextFontEngine::faceId() const
+{
+ return QFontEngine::FaceId();
+}
+
+bool QCoreTextFontEngine::canRender(const QChar *string, int len)
+{
+ QCFType<CTFontRef> retFont = CTFontCreateForString(ctfont,
+ QCFType<CFStringRef>(CFStringCreateWithCharactersNoCopy(0,
+ reinterpret_cast<const UniChar *>(string),
+ len, kCFAllocatorNull)),
+ CFRangeMake(0, len));
+ return retFont != 0;
+ return false;
+}
+
+ bool QCoreTextFontEngine::getSfntTableData(uint tag, uchar *buffer, uint *length) const
+ {
+ QCFType<CFDataRef> 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 *)
+{
+ // ###
+}
+
+#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+
+#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, <CTStringAttributes.h>
+ 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<QFontEngineMac *>(static_cast<const QFontEngineMac *>(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;
+};
+
+static OSStatus atsuPostLayoutCallback(ATSULayoutOperationSelector selector, ATSULineRef lineRef, URefCon refCon,
+ void *operationExtraParameter, ATSULayoutOperationCallbackStatus *callbackStatus)
+{
+ Q_UNUSED(selector);
+ Q_UNUSED(operationExtraParameter);
+
+ QGlyphLayoutInfo *nfo = reinterpret_cast<QGlyphLayoutInfo *>(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);
+ }
+ Q_ASSERT(*nfo->numGlyphs == item->length - surrogates);
+#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 (glyphId != 0xffff || i == 0) {
+ 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<QFontEngineMacMulti *>(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;
+
+ QVarLengthArray<int> 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 (!(flags & QTextEngine::DesignMetrics)) {
+ layopts |= kATSLineFractDisable | kATSLineUseDeviceMetrics
+ | kATSLineDisableAutoAdjustDisplayPos;
+ }
+
+ 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);
+ 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<const UniChar *>(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<quint16>(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<quint16>(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;
+}
+
+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<const uchar *>(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<GlyphID> atsuGlyphs(glyphs->numGlyphs);
+ for (int i = 0; i < glyphs->numGlyphs; ++i)
+ atsuGlyphs[i] = glyphs->glyphs[i];
+
+ QVarLengthArray<ATSGlyphScreenMetrics> 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);
+ }
+}
+
+glyph_metrics_t QFontEngineMac::boundingBox(const QGlyphLayout &glyphs)
+{
+ QFixed w;
+ 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 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);
+
+ return gm;
+}
+
+QFixed QFontEngineMac::ascent() const
+{
+ return m_ascent;
+}
+
+QFixed QFontEngineMac::descent() const
+{
+ return m_descent;
+}
+
+QFixed QFontEngineMac::leading() const
+{
+ return m_leading;
+}
+
+qreal QFontEngineMac::maxCharWidth() const
+{
+ return m_maxCharWidth;
+}
+
+QFixed QFontEngineMac::xHeight() const
+{
+ return m_xHeight;
+}
+
+QFixed QFontEngineMac::averageCharWidth() const
+{
+ return 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);
+}
+
+QImage QFontEngineMac::alphaMapForGlyph(glyph_t glyph)
+{
+ const glyph_metrics_t br = boundingBox(glyph);
+ QImage im(qRound(br.width)+2, qRound(br.height)+4, QImage::Format_RGB32);
+ im.fill(0);
+
+ CGColorSpaceRef colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace();
+#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
+ uint cgflags = kCGImageAlphaNoneSkipFirst;
+#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version
+ if(QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4)
+ cgflags |= kCGBitmapByteOrder32Host;
+#endif
+#else
+ CGImageAlphaInfo cgflags = kCGImageAlphaNoneSkipFirst;
+#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, false);
+ 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);
+
+ QImage indexed(im.width(), im.height(), QImage::Format_Indexed8);
+ QVector<QRgb> colors(256);
+ for (int i=0; i<256; ++i)
+ colors[i] = qRgba(0, 0, 0, i);
+ indexed.setColorTable(colors);
+
+ for (int y=0; y<im.height(); ++y) {
+ uint *src = (uint*) im.scanLine(y);
+ uchar *dst = indexed.scanLine(y);
+ for (int x=0; x<im.width(); ++x) {
+ *dst = qGray(*src);
+ ++dst;
+ ++src;
+ }
+ }
+
+ return indexed;
+}
+
+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<QFixedPoint> positions;
+ QVarLengthArray<glyph_t> 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<CGSize> advances(glyphs.size());
+ QVarLengthArray<CGGlyph> 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)
+ // 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
+ 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());
+#endif
+ 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<quint16>(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<quint16>(bbox.xMin);
+ bbox.yMin = qFromBigEndian<quint16>(bbox.yMin);
+ bbox.xMax = qFromBigEndian<quint16>(bbox.xMax);
+ bbox.yMax = qFromBigEndian<quint16>(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<quint16>(metrics.ascender);
+ metrics.descender = qFromBigEndian<quint16>(metrics.descender);
+ metrics.linegap = qFromBigEndian<quint16>(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<quint16>(lw);
+ props.lineWidth = lw;
+
+ // CTFontCopyPostScriptName
+ QCFString psName;
+ if (ATSFontGetPostScriptName(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &psName) == noErr)
+ props.postscriptName = QString(psName).toUtf8();
+ props.postscriptName = QPdf::stripSpecialCharacters(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_p.h b/src/gui/text/qfontengine_p.h
new file mode 100644
index 0000000000..e289aa9174
--- /dev/null
+++ b/src/gui/text/qfontengine_p.h
@@ -0,0 +1,617 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <QtCore/qvarlengtharray.h>
+#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 "qfontengineglyphcache_p.h"
+
+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
+{
+ Q_OBJECT
+public:
+ enum Type {
+ Box,
+ Multi,
+
+ // X11 types
+ XLFD,
+
+ // MS Windows types
+ Win,
+
+ // Apple Mac OS types
+ Mac,
+
+ // Trolltech QWS types
+ Freetype,
+ QPF1,
+ QPF2,
+ Proxy,
+ TestFontEngine = 0x1000
+ };
+
+ 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 QFixed emSquareSize() const { return ascent(); }
+
+ /* returns 0 as glyph index for non existant 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)
+ 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<glyph_t> &glyphs_out, QVarLengthArray<QFixedPoint> &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 transparant to 255=opaque
+ */
+ virtual QImage alphaMapForGlyph(glyph_t) = 0;
+ virtual QImage alphaMapForGlyph(glyph_t, const QTransform &t);
+ virtual QImage alphaRGBMapForGlyph(glyph_t, int margin, const QTransform &t);
+
+ 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 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);
+ void setGlyphCache(QFontEngineGlyphCache::Type key, QFontEngineGlyphCache *data);
+ QFontEngineGlyphCache *glyphCache(void *key, const QTransform &transform) const;
+ QFontEngineGlyphCache *glyphCache(QFontEngineGlyphCache::Type key, 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);
+
+ 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)
+ struct KernPair {
+ uint left_right;
+ QFixed adjust;
+
+ inline bool operator<(const KernPair &other) const
+ {
+ return left_right < other.left_right;
+ }
+ };
+ QVector<KernPair> kerning_pairs;
+ void loadKerningPairs(QFixed scalingFactor);
+#endif
+
+ int glyphFormat;
+
+private:
+ /// remove old entries from the glyph cache. Helper method for the setGlyphCache ones.
+ void expireGlyphCache();
+
+ GlyphPointerHash m_glyphPointerHash;
+ GlyphIntHash m_glyphIntHash;
+ mutable QList<QFontEngineGlyphCache*> m_glyphCacheQueue;
+};
+
+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)
+ 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 Q_GUI_EXPORT 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 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;
+
+protected:
+ friend class QPSPrintEnginePrivate;
+ friend class QPSPrintEngineFontMulti;
+ virtual void loadEngine(int at) = 0;
+ QVector<QFontEngine *> engines;
+};
+
+#if defined(Q_WS_MAC)
+
+struct QCharAttributes;
+class QFontEngineMacMulti;
+# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+class QCoreTextFontEngineMulti;
+class QCoreTextFontEngine : public QFontEngine
+{
+public:
+ QCoreTextFontEngine(CTFontRef font, const QFontDef &def,
+ QCoreTextFontEngineMulti *multiEngine = 0);
+ ~QCoreTextFontEngine();
+ virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const;
+ virtual void recalcAdvances(int , 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 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);
+ virtual qreal minRightBearing() const;
+ virtual qreal minLeftBearing() const;
+
+
+
+private:
+ CTFontRef ctfont;
+ CGFontRef cgFont;
+ QCoreTextFontEngineMulti *parentEngine;
+ int synthesisFlags;
+ friend class QCoreTextFontEngineMulti;
+};
+
+class QCoreTextFontEngineMulti : public QFontEngineMulti
+{
+public:
+ QCoreTextFontEngineMulti(const ATSFontFamilyRef &atsFamily, const ATSFontRef &atsFontRef,
+ 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 void recalcAdvances(int , QGlyphLayout *, QTextEngine::ShaperFlags) const;
+ virtual void doKerning(int , QGlyphLayout *, QTextEngine::ShaperFlags) const;
+
+ virtual const char *name() const { return "CoreText"; }
+protected:
+ virtual void loadEngine(int at);
+
+private:
+ inline const QCoreTextFontEngine *engineAt(int i) const
+ { return static_cast<const QCoreTextFontEngine *>(engines.at(i)); }
+
+ uint fontIndexForFont(CTFontRef id) const;
+ CTFontRef ctfont;
+ mutable QCFType<CFDictionaryRef> attributeDict;
+
+ friend class QFontDialogPrivate;
+};
+# endif //MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+
+#ifndef QT_MAC_USE_COCOA
+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);
+
+private:
+ ATSUFontID fontID;
+ QCFType<CGFontRef> 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<const QFontEngineMac *>(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
+
+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
+
+#endif // QFONTENGINE_P_H
diff --git a/src/gui/text/qfontengine_qpf.cpp b/src/gui/text/qfontengine_qpf.cpp
new file mode 100644
index 0000000000..e9fcac4cd8
--- /dev/null
+++ b/src/gui/text/qfontengine_qpf.cpp
@@ -0,0 +1,1161 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qfontengine_qpf_p.h"
+
+#include "private/qpaintengine_raster_p.h"
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qfileinfo.h>
+
+#include <QtCore/qfile.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qbuffer.h>
+#if !defined(QT_NO_FREETYPE)
+#include "private/qfontengine_ft_p.h"
+#endif
+
+// for mmap
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+
+QT_BEGIN_NAMESPACE
+
+#ifndef QT_NO_QWS_QPF2
+
+QT_BEGIN_INCLUDE_NAMESPACE
+#include "qpfutil.cpp"
+
+#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<type>(tagPtr); \
+ DEBUG_VERIFY() << "read value" << variable << "of type " #type; \
+ tagPtr += sizeof(type)
+
+template <typename T>
+T readValue(const uchar *&data)
+{
+ T value = qFromBigEndian<T>(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<const quint32 *>(fontData + glyphMapOffset);
+ quint32 glyphPos = qFromBigEndian<quint32>(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<QFontEngineQPF *>(this)->remapFontData();
+#endif
+ if (glyphPos > glyphDataSize)
+ return 0;
+ }
+ return reinterpret_cast<const Glyph *>(fontData + glyphDataOffset + glyphPos);
+}
+
+bool QFontEngineQPF::verifyHeader(const uchar *data, int size)
+{
+ VERIFY(size >= int(sizeof(Header)));
+ const Header *header = reinterpret_cast<const Header *>(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<quint16>(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<const Header *>(data);
+ const uchar *tagPtr = data + sizeof(Header);
+ const uchar *endPtr = tagPtr + qFromBigEndian<quint16>(header->dataSize);
+ while (tagPtr < endPtr - 3) {
+ quint16 tag = readValue<quint16>(tagPtr);
+ quint16 length = readValue<quint16>(tagPtr);
+ if (tag == requestedTag) {
+ switch (tagTypes[requestedTag]) {
+ case StringType:
+ return QVariant(QString::fromUtf8(reinterpret_cast<const char *>(tagPtr), length));
+ case UInt32Type:
+ return QVariant(readValue<quint32>(tagPtr));
+ case UInt8Type:
+ return QVariant(uint(*tagPtr));
+ case FixedType:
+ return QVariant(QFixed::fromFixed(readValue<quint32>(tagPtr)).toReal());
+ case BitFieldType:
+ return QVariant(QByteArray(reinterpret_cast<const char *>(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<QByteArray> QFontEngineQPF::cleanUpAfterClientCrash(const QList<int> &crashedClientIds)
+{
+ QList<QByteArray> 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 = ::open(fileName.constData(), O_RDONLY);
+ 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<QFontEngineQPF::Header *>(header)->lock;
+
+ if (lockValue && crashedClientIds.contains(lockValue)) {
+ removedFonts.append(fileName);
+ QFile::remove(QFile::decodeName(fileName));
+ }
+
+ ::munmap(header, sizeof(QFontEngineQPF::Header));
+ }
+ ::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;
+ 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() + QLatin1String("_")
+ + QString::number(fontDef.pixelSize)
+ + QLatin1String("_") + QString::number(fontDef.weight)
+ + (fontDef.style != QFont::StyleNormal ?
+ QLatin1String("_italic") : QLatin1String(""))
+ + QLatin1String(".qsf");
+ fileName.replace(QLatin1Char(' '), QLatin1Char('_'));
+ fileName.prepend(qws_fontCacheDir());
+
+ const QByteArray encodedName = QFile::encodeName(fileName);
+ if (::access(encodedName, F_OK) == 0) {
+#if defined(DEBUG_FONTENGINE)
+ qDebug() << "found existing qpf:" << fileName;
+#endif
+ if (::access(encodedName, W_OK | R_OK) == 0)
+ fd = ::open(encodedName, O_RDWR);
+ else if (::access(encodedName, R_OK) == 0)
+ fd = ::open(encodedName, O_RDONLY);
+ } else {
+#if defined(DEBUG_FONTENGINE)
+ qDebug() << "creating qpf on the fly:" << fileName;
+#endif
+ if (::access(QFile::encodeName(qws_fontCacheDir()), W_OK) == 0) {
+ fd = ::open(encodedName, O_RDWR | O_EXCL | O_CREAT, 0644);
+
+ QBuffer buffer;
+ buffer.open(QIODevice::ReadWrite);
+ QPFGenerator generator(&buffer, renderingFontEngine);
+ generator.generate();
+ buffer.close();
+ const QByteArray &data = buffer.data();
+ ::write(fd, data.constData(), data.size());
+ }
+ }
+ }
+
+ QT_STATBUF st;
+ if (QT_FSTAT(fd, &st)) {
+#if defined(DEBUG_FONTENGINE)
+ qDebug() << "stat 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<const Header *>(fontData);
+
+ readOnly = (header->lock == 0xffffffff);
+
+ const uchar *data = fontData + sizeof(Header) + qFromBigEndian<quint16>(header->dataSize);
+ const uchar *endPtr = fontData + dataSize;
+ while (data <= endPtr - 8) {
+ quint16 blockTag = readValue<quint16>(data);
+ data += 2; // skip padding
+ quint32 blockSize = readValue<quint32>(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<quint32>(data) != qpfTtfRevision) {
+ freetype->release(face_id);
+ freetype = 0;
+ }
+ }
+ if (!cmapOffset && freetype) {
+ freetypeCMapTable = getSfntTable(MAKE_TAG('c', 'm', 'a', 'p'));
+ externalCMap = reinterpret_cast<const uchar *>(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<const quint32 *>(fontData + glyphMapOffset);
+ for (uint i = 0; i < glyphMapEntries; ++i) {
+ quint32 glyphDataPos = qFromBigEndian<quint32>(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, QFile::encodeName(fileName));
+#endif
+}
+
+QFontEngineQPF::~QFontEngineQPF()
+{
+#if defined(Q_WS_QWS)
+ if (isValid() && renderingFontEngine)
+ qt_fbdpy->sendFontCommand(QWSFontCommand::StoppedUsingFont, QFile::encodeName(fileName));
+#endif
+ delete renderingFontEngine;
+ if (fontData)
+ munmap((void *)fontData, dataSize);
+ 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<QFontEngineQPF *>(this)->ensureGlyphsLoaded(*glyphs);
+#endif
+ return true;
+ }
+
+ if (*nglyphs < len) {
+ *nglyphs = len;
+ return false;
+ }
+
+#if defined(DEBUG_FONTENGINE)
+ QSet<QChar> 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 QFontEngineQPF::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const
+{
+#ifndef QT_NO_FREETYPE
+ const_cast<QFontEngineQPF *>(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)
+ 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, 0xff000000 | j | (j<<8) | (j<<16));
+
+ for (int i=0; i<glyph->height; ++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<QRasterPaintEngine*>(p);
+
+ QTransform matrix = pState->transform();
+ matrix.translate(_x, _y);
+ QFixed x = QFixed::fromReal(matrix.dx());
+ QFixed y = QFixed::fromReal(matrix.dy());
+
+ QVarLengthArray<QFixedPoint> positions;
+ QVarLengthArray<glyph_t> 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<const uchar *>(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<QProxyFontEngine *>(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<QFontEngineQPF *>(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<QFontEngineQPF *>(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<qreal>());
+}
+
+QFixed QFontEngineQPF::descent() const
+{
+ return QFixed::fromReal(extractHeaderField(fontData, Tag_Descent).value<qreal>());
+}
+
+QFixed QFontEngineQPF::leading() const
+{
+ return QFixed::fromReal(extractHeaderField(fontData, Tag_Leading).value<qreal>());
+}
+
+qreal QFontEngineQPF::maxCharWidth() const
+{
+ return extractHeaderField(fontData, Tag_MaxCharWidth).value<qreal>();
+}
+
+qreal QFontEngineQPF::minLeftBearing() const
+{
+ return extractHeaderField(fontData, Tag_MinLeftBearing).value<qreal>();
+}
+
+qreal QFontEngineQPF::minRightBearing() const
+{
+ return extractHeaderField(fontData, Tag_MinRightBearing).value<qreal>();
+}
+
+QFixed QFontEngineQPF::underlinePosition() const
+{
+ return QFixed::fromReal(extractHeaderField(fontData, Tag_UnderlinePosition).value<qreal>());
+}
+
+QFixed QFontEngineQPF::lineThickness() const
+{
+ return QFixed::fromReal(extractHeaderField(fontData, Tag_LineThickness).value<qreal>());
+}
+
+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 = fontDef.pixelSize << 6;
+ 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<QFontEngineQPF *>(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).convertToFormat(QImage::Format_Indexed8);
+ 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);
+
+ ::write(fd, &g, sizeof(g));
+ ::write(fd, img.bits(), img.numBytes());
+
+ 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.numBytes();
+ 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<uchar *>(::mremap(const_cast<uchar *>(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<quint32>(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<quint16>(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
+
+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..a9b87ff981
--- /dev/null
+++ b/src/gui/text/qfontengine_qpf_p.h
@@ -0,0 +1,298 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <qendian.h>
+#include <qbuffer.h>
+
+#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<QByteArray> cleanUpAfterClientCrash(const QList<int> &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;
+ 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..d77632990f
--- /dev/null
+++ b/src/gui/text/qfontengine_qws.cpp
@@ -0,0 +1,625 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qfontengine_p.h"
+#include <private/qunicodetables_p.h>
+#include <qwsdisplay_qws.h>
+#include <qvarlengtharray.h>
+#include <private/qpainter_p.h>
+#include <private/qpaintengine_raster_p.h>
+#include <private/qpdf_p.h>
+#include "qtextengine_p.h"
+
+#include <qdebug.h>
+
+
+#ifndef QT_NO_QWS_QPF
+
+#include "qfile.h"
+#include "qdir.h"
+
+#define QT_USE_MMAP
+#include <stdlib.h>
+
+#ifdef QT_USE_MMAP
+// for mmap
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+
+# 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; i<indent; i++) printf(" ");
+ printf("%d..%d",min,max);
+ //if ( indent == 0 )
+ printf(" (total %d)",totalChars());
+ printf("\n");
+ if ( less ) less->dump(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)
+ {
+ uchar rw = f.getch();
+ uchar cl = f.getch();
+ min = (rw << 8) | cl;
+ rw = f.getch();
+ cl = f.getch();
+ max = (rw << 8) | cl;
+ int flags = f.getch();
+ 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; i<n; i++) {
+ glyph[i].metrics = (QPFGlyphMetrics*)data;
+ data += sizeof(QPFGlyphMetrics);
+ }
+ if ( less )
+ less->readMetrics(data);
+ if ( more )
+ more->readMetrics(data);
+ }
+#else
+ void readMetrics(QIODevice& f)
+ {
+ int n = max-min+1;
+ for (int i=0; i<n; i++) {
+ glyph[i].metrics = new QPFGlyphMetrics;
+ f.readBlock((char*)glyph[i].metrics, sizeof(QPFGlyphMetrics));
+ }
+ if ( less )
+ less->readMetrics(f);
+ if ( more )
+ more->readMetrics(f);
+ }
+#endif
+
+#ifdef QT_USE_MMAP
+ void readData(uchar*& data)
+ {
+ int n = max-min+1;
+ for (int i=0; i<n; i++) {
+ QSize s( glyph[i].metrics->width, 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; i<n; i++) {
+ QSize s( glyph[i].metrics->width, 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.readBlock((char*)glyph[i].data, datasize);
+ }
+ if ( less )
+ less->readData(f);
+ if ( more )
+ more->readData(f);
+ }
+#endif
+
+};
+
+class QFontEngineQPF1Data
+{
+public:
+ QPFFontMetrics fm;
+ QPFGlyphTree *tree;
+};
+
+
+QFontEngineQPF1::QFontEngineQPF1(const QFontDef&, const QString &fn)
+{
+ cache_cost = 1;
+
+ int f = ::open( QFile::encodeName(fn), O_RDONLY );
+ Q_ASSERT(f>=0);
+ QT_STATBUF st;
+ if ( QT_FSTAT( f, &st ) )
+ qFatal("Failed to stat %s",QFile::encodeName(fn).data());
+ uchar* data = (uchar*)mmap( 0, // any address
+ st.st_size, // whole file
+ PROT_READ, // read-only memory
+#if !defined(Q_OS_SOLARIS) && !defined(Q_OS_QNX4) && !defined(Q_OS_INTEGRITY)
+ MAP_FILE | MAP_PRIVATE, // swap-backed map from file
+#else
+ MAP_PRIVATE,
+#endif
+ f, 0 ); // from offset 0 of f
+#if defined(Q_OS_QNX4) && !defined(MAP_FAILED)
+#define MAP_FAILED ((void *)-1)
+#endif
+ if ( !data || data == (uchar*)MAP_FAILED )
+ qFatal("Failed to mmap %s",QFile::encodeName(fn).data());
+ ::close(f);
+
+ d = new QFontEngineQPF1Data;
+ memcpy(reinterpret_cast<char*>(&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()
+{
+ 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<QRasterPaintEngine*>(p);
+
+ QTransform matrix = pState->transform();
+ matrix.translate(_x, _y);
+ QFixed x = QFixed::fromReal(matrix.dx());
+ QFixed y = QFixed::fromReal(matrix.dy());
+
+ QVarLengthArray<QFixedPoint> positions;
+ QVarLengthArray<glyph_t> 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);
+ } else {
+ image = QImage(glyph->metrics->width, glyph->metrics->height, QImage::Format_Indexed8);
+ for (int j=0; j<256; ++j)
+ image.setColor(j, 0xff000000 | j | (j<<8) | (j<<16));
+ }
+ for (int i=0; i<glyph->metrics->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, 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_win.cpp b/src/gui/text/qfontengine_win.cpp
new file mode 100644
index 0000000000..1996d44a4e
--- /dev/null
+++ b/src/gui/text/qfontengine_win.cpp
@@ -0,0 +1,1575 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qfontengine_p.h"
+#include "qtextengine_p.h"
+#include <qglobal.h>
+#include "qt_windows.h"
+#include <private/qapplication_p.h>
+
+#include <qlibrary.h>
+#include <qpaintdevice.h>
+#include <qpainter.h>
+#include <qlibrary.h>
+#include <limits.h>
+
+#include <qendian.h>
+#include <qmath.h>
+#include <qthreadstorage.h>
+
+#include <private/qunicodetables_p.h>
+#include <qbitmap.h>
+
+#include <private/qpainter_p.h>
+#include <private/qpdf_p.h>
+#include "qpaintengine.h"
+#include "qvarlengtharray.h"
+#include <private/qpaintengine_raster_p.h>
+#include <private/qnativeimage_p.h>
+
+#if defined(Q_OS_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)) \
+ )
+
+typedef BOOL (WINAPI *PtrGetCharWidthI)(HDC, UINT, UINT, LPWORD, LPINT);
+
+// 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<QtHDC *>, 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
+
+static HFONT stock_sysfont = 0;
+
+static PtrGetCharWidthI ptrGetCharWidthI = 0;
+static bool resolvedGetCharWidthI = false;
+
+static void resolveGetCharWidthI()
+{
+ if (resolvedGetCharWidthI)
+ return;
+ resolvedGetCharWidthI = true;
+ ptrGetCharWidthI = (PtrGetCharWidthI)QLibrary::resolve(QLatin1String("gdi32"), "GetCharWidthI");
+}
+
+// Copy a LOGFONTW struct into a LOGFONTA by converting the face name to an 8 bit value.
+// This is needed when calling CreateFontIndirect on non-unicode windowses.
+inline static void wa_copy_logfont(LOGFONTW *lfw, LOGFONTA *lfa)
+{
+ lfa->lfHeight = lfw->lfHeight;
+ lfa->lfWidth = lfw->lfWidth;
+ lfa->lfEscapement = lfw->lfEscapement;
+ lfa->lfOrientation = lfw->lfOrientation;
+ lfa->lfWeight = lfw->lfWeight;
+ lfa->lfItalic = lfw->lfItalic;
+ lfa->lfUnderline = lfw->lfUnderline;
+ lfa->lfCharSet = lfw->lfCharSet;
+ lfa->lfOutPrecision = lfw->lfOutPrecision;
+ lfa->lfClipPrecision = lfw->lfClipPrecision;
+ lfa->lfQuality = lfw->lfQuality;
+ lfa->lfPitchAndFamily = lfw->lfPitchAndFamily;
+
+ QString fam = QString::fromUtf16((const ushort*)lfw->lfFaceName);
+ memcpy(lfa->lfFaceName, fam.toLocal8Bit().constData(), fam.length() + 1);
+}
+
+// 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;
+}
+
+static inline HFONT systemFont()
+{
+ if (stock_sysfont == 0)
+ stock_sysfont = (HFONT)GetStockObject(SYSTEM_FONT);
+ return stock_sysfont;
+}
+
+
+// general font engine
+
+QFixed QFontEngineWin::lineThickness() const
+{
+ if(lineWidth > 0)
+ return lineWidth;
+
+ return QFontEngine::lineThickness();
+}
+
+#if defined(Q_OS_WINCE)
+static OUTLINETEXTMETRICW *getOutlineTextMetric(HDC hdc)
+{
+ int size;
+ size = GetOutlineTextMetricsW(hdc, 0, 0);
+ OUTLINETEXTMETRICW *otm = (OUTLINETEXTMETRICW *)malloc(size);
+ GetOutlineTextMetricsW(hdc, size, otm);
+ return otm;
+}
+#else
+static OUTLINETEXTMETRICA *getOutlineTextMetric(HDC hdc)
+{
+ int size;
+ size = GetOutlineTextMetricsA(hdc, 0, 0);
+ OUTLINETEXTMETRICA *otm = (OUTLINETEXTMETRICA *)malloc(size);
+ GetOutlineTextMetricsA(hdc, size, otm);
+ return otm;
+}
+#endif
+
+void QFontEngineWin::getCMap()
+{
+ QT_WA({
+ ttf = (bool)(tm.w.tmPitchAndFamily & TMPF_TRUETYPE);
+ } , {
+ ttf = (bool)(tm.a.tmPitchAndFamily & TMPF_TRUETYPE);
+ });
+ HDC hdc = shared_dc();
+ SelectObject(hdc, hfont);
+ bool symb = false;
+ if (ttf) {
+ cmapTable = getSfntTable(qbswap<quint32>(MAKE_TAG('c', 'm', 'a', 'p')));
+ int size = 0;
+ cmap = QFontEngine::getCMap(reinterpret_cast<const uchar *>(cmapTable.constData()),
+ cmapTable.size(), &symb, &size);
+ }
+ if (!cmap) {
+ ttf = false;
+ symb = false;
+ }
+ symbol = symb;
+ designToDevice = 1;
+ _faceId.index = 0;
+ if(cmap) {
+#if defined(Q_OS_WINCE)
+ OUTLINETEXTMETRICW *otm = getOutlineTextMetric(hdc);
+#else
+ OUTLINETEXTMETRICA *otm = getOutlineTextMetric(hdc);
+#endif
+ designToDevice = QFixed((int)otm->otmEMSquare)/int(otm->otmTextMetrics.tmHeight);
+ unitsPerEm = otm->otmEMSquare;
+ x_height = (int)otm->otmsXHeight;
+ loadKerningPairs(designToDevice);
+ _faceId.filename = (char *)otm + (int)otm->otmpFullName;
+ lineWidth = otm->otmsUnderscoreSize;
+ fsType = otm->otmfsType;
+ free(otm);
+ } else {
+ unitsPerEm = tm.w.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_OS_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
+ ushort first, last;
+ QT_WA({
+ first = tm.w.tmFirstChar;
+ last = tm.w.tmLastChar;
+ }, {
+ first = tm.a.tmFirstChar;
+ last = tm.a.tmLastChar;
+ });
+ for (; i < numChars; ++i, ++glyph_pos) {
+ uint ucs = QChar::mirroredChar(getChar(str, i, numChars));
+ if (
+#ifdef Q_OS_WINCE
+ tm.w.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_OS_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
+ ushort first, last;
+ QT_WA({
+ first = tm.w.tmFirstChar;
+ last = tm.w.tmLastChar;
+ }, {
+ first = tm.a.tmFirstChar;
+ last = tm.a.tmLastChar;
+ });
+ for (; i < numChars; ++i, ++glyph_pos) {
+ uint uc = getChar(str, i, numChars);
+ if (
+#ifdef Q_OS_WINCE
+ tm.w.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;
+ QT_WA({
+ res = GetTextMetricsW(hdc, &tm.w);
+ } , {
+ res = GetTextMetricsA(hdc, &tm.a);
+ });
+ fontDef.fixedPitch = !(tm.w.tmPitchAndFamily & TMPF_FIXED_PITCH);
+ if (!res)
+ qErrnoWarning("QFontEngineWin: GetTextMetrics failed");
+
+ cache_cost = tm.w.tmHeight * tm.w.tmAveCharWidth * 2000;
+ getCMap();
+
+ useTextOutA = false;
+#ifndef Q_OS_WINCE
+ // TextOutW doesn't work for symbol fonts on Windows 95!
+ // since we're using glyph indices we don't care for ttfs about this!
+ if (QSysInfo::WindowsVersion == QSysInfo::WV_95 && !ttf &&
+ (_name == QLatin1String("Marlett") || _name == QLatin1String("Symbol") ||
+ _name == QLatin1String("Webdings") || _name == QLatin1String("Wingdings")))
+ useTextOutA = true;
+#endif
+ widthCache = 0;
+ widthCacheSize = 0;
+ designAdvances = 0;
+ designAdvancesSize = 0;
+
+ if (!resolvedGetCharWidthI)
+ resolveGetCharWidthI();
+}
+
+QFontEngineWin::~QFontEngineWin()
+{
+ if (designAdvances)
+ free(designAdvances);
+
+ if (widthCache)
+ free(widthCache);
+
+ // make sure we aren't by accident still selected
+ SelectObject(shared_dc(), systemFont());
+
+ if (!stockFont) {
+ if (!DeleteObject(hfont))
+ qErrnoWarning("QFontEngineWin: failed to delete non-stock font...");
+ }
+}
+
+HGDIOBJ QFontEngineWin::selectDesignFont(QFixed *overhang) const
+{
+ LOGFONT f = logfont;
+ f.lfHeight = unitsPerEm;
+ HFONT designFont;
+ QT_WA({
+ designFont = CreateFontIndirectW(&f);
+ }, {
+ LOGFONTA fa;
+ wa_copy_logfont(&f, &fa);
+ designFont = CreateFontIndirectA(&fa);
+ });
+ HGDIOBJ oldFont = SelectObject(shared_dc(), designFont);
+
+ if (QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based) {
+ BOOL res;
+ QT_WA({
+ TEXTMETRICW tm;
+ res = GetTextMetricsW(shared_dc(), &tm);
+ if (!res)
+ qErrnoWarning("QFontEngineWin: GetTextMetrics failed");
+ *overhang = QFixed((int)tm.tmOverhang) / designToDevice;
+ } , {
+ TEXTMETRICA tm;
+ res = GetTextMetricsA(shared_dc(), &tm);
+ if (!res)
+ qErrnoWarning("QFontEngineWin: GetTextMetrics failed");
+ *overhang = QFixed((int)tm.tmOverhang) / designToDevice;
+ });
+ } else {
+ *overhang = 0;
+ }
+ return oldFont;
+}
+
+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;
+
+#if defined(Q_OS_WINCE)
+ HDC hdc = shared_dc();
+ if (flags & QTextEngine::DesignMetrics) {
+ HGDIOBJ oldFont = 0;
+ QFixed overhang = 0;
+
+ int glyph_pos = 0;
+ for(register 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);
+ unsigned int glyph = glyphs->glyphs[glyph_pos];
+ if(int(glyph) >= designAdvancesSize) {
+ int newSize = (glyph + 256) >> 8 << 8;
+ designAdvances = (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(&overhang);
+ SIZE size = {0, 0};
+ GetTextExtentPoint32W(hdc, (wchar_t *)(str+i), surrogate ? 2 : 1, &size);
+ designAdvances[glyph] = QFixed((int)size.cx)/designToDevice;
+ }
+ glyphs->advances_x[glyph_pos] = designAdvances[glyph];
+ glyphs->advances_y[glyph_pos] = 0;
+ if (surrogate)
+ ++i;
+ ++glyph_pos;
+ }
+ if(oldFont)
+ DeleteObject(SelectObject(hdc, oldFont));
+ } else {
+ int glyph_pos = 0;
+ HGDIOBJ oldFont = 0;
+
+ for(register 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);
+ unsigned int glyph = glyphs->glyphs[glyph_pos];
+
+ glyphs->advances_y[glyph_pos] = 0;
+
+ if (glyph >= widthCacheSize) {
+ int newSize = (glyph + 256) >> 8 << 8;
+ widthCache = (unsigned char *)realloc(widthCache, newSize*sizeof(QFixed));
+ memset(widthCache + widthCacheSize, 0, newSize - widthCacheSize);
+ widthCacheSize = newSize;
+ }
+ glyphs->advances_x[glyph_pos] = widthCache[glyph];
+ // font-width cache failed
+ if (glyphs->advances_x[glyph_pos] == 0) {
+ SIZE size = {0, 0};
+ if (!oldFont)
+ oldFont = SelectObject(hdc, hfont);
+ GetTextExtentPoint32W(hdc, (wchar_t *)str + i, surrogate ? 2 : 1, &size);
+ glyphs->advances_x[glyph_pos] = size.cx;
+ // if glyph's within cache range, store it for later
+ if (size.cx > 0 && size.cx < 0x100)
+ widthCache[glyph] = size.cx;
+ }
+
+ if (surrogate)
+ ++i;
+ ++glyph_pos;
+ }
+
+ if (oldFont)
+ SelectObject(hdc, oldFont);
+ }
+#else
+ recalcAdvances(glyphs, flags);
+#endif
+ return true;
+}
+
+void QFontEngineWin::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const
+{
+ HGDIOBJ oldFont = 0;
+ HDC hdc = shared_dc();
+ if (ttf && (flags & QTextEngine::DesignMetrics)) {
+ QFixed overhang = 0;
+
+ 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 = (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(&overhang);
+
+ if (ptrGetCharWidthI) {
+ int width = 0;
+ ptrGetCharWidthI(hdc, glyph, 1, 0, &width);
+
+ designAdvances[glyph] = QFixed(width) / designToDevice;
+ } else {
+#ifndef Q_OS_WINCE
+ GLYPHMETRICS gm;
+ DWORD res = GDI_ERROR;
+ 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;
+ QT_WA({
+ res = GetGlyphOutlineW(hdc, glyph, GGO_METRICS|GGO_GLYPH_INDEX|GGO_NATIVE, &gm, 0, 0, &mat);
+ } , {
+ res = GetGlyphOutlineA(hdc, glyph, GGO_METRICS|GGO_GLYPH_INDEX|GGO_NATIVE, &gm, 0, 0, &mat);
+ });
+
+ if (res != GDI_ERROR) {
+ designAdvances[glyph] = QFixed(gm.gmCellIncX) / designToDevice;
+ }
+#endif
+ }
+ }
+ glyphs->advances_x[i] = designAdvances[glyph];
+ glyphs->advances_y[i] = 0;
+ }
+ if(oldFont)
+ DeleteObject(SelectObject(hdc, oldFont));
+ } else {
+ int overhang = (QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based) ? tm.a.tmOverhang : 0;
+
+ 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 = (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};
+ GetTextExtentPoint32W(hdc, (wchar_t *)ch, chrLen, &size);
+ width = size.cx;
+ } else if (ptrGetCharWidthI) {
+ ptrGetCharWidthI(hdc, glyph, 1, 0, &width);
+
+ width -= overhang;
+ } else {
+#ifndef Q_OS_WINCE
+ GLYPHMETRICS gm;
+ DWORD res = GDI_ERROR;
+ 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;
+ QT_WA({
+ res = GetGlyphOutlineW(hdc, glyph, GGO_METRICS|GGO_GLYPH_INDEX, &gm, 0, 0, &mat);
+ } , {
+ res = GetGlyphOutlineA(hdc, glyph, GGO_METRICS|GGO_GLYPH_INDEX, &gm, 0, 0, &mat);
+ });
+
+ if (res != GDI_ERROR) {
+ width = gm.gmCellIncX;
+ }
+#endif
+ }
+ 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.w.tmAscent, w, tm.w.tmHeight, w, 0);
+}
+
+
+
+
+#ifndef Q_OS_WINCE
+typedef HRESULT (WINAPI *pGetCharABCWidthsFloat)(HDC, UINT, UINT, LPABCFLOAT);
+static pGetCharABCWidthsFloat qt_GetCharABCWidthsFloat = 0;
+#endif
+
+glyph_metrics_t QFontEngineWin::boundingBox(glyph_t glyph, const QTransform &t)
+{
+#ifndef Q_OS_WINCE
+ GLYPHMETRICS gm;
+
+ HDC hdc = shared_dc();
+ SelectObject(hdc, hfont);
+ if(!ttf) {
+ SIZE s = {0, 0};
+ WCHAR ch = glyph;
+ int width;
+ int overhang = 0;
+ static bool resolved = false;
+ if (!resolved) {
+ QLibrary lib(QLatin1String("gdi32"));
+ qt_GetCharABCWidthsFloat = (pGetCharABCWidthsFloat) lib.resolve("GetCharABCWidthsFloatW");
+ resolved = true;
+ }
+ if (QT_WA_INLINE(true, false) && qt_GetCharABCWidthsFloat) {
+ ABCFLOAT abc;
+ qt_GetCharABCWidthsFloat(hdc, ch, ch, &abc);
+ width = qRound(abc.abcfB);
+ } else {
+ GetTextExtentPoint32W(hdc, &ch, 1, &s);
+ overhang = (QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based) ? tm.a.tmOverhang : 0;
+ width = s.cx;
+ }
+
+ return glyph_metrics_t(0, -tm.a.tmAscent,
+ width, tm.a.tmHeight,
+ width-overhang, 0).transformed(t);
+ } else {
+ 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);
+ }
+
+ QT_WA({
+ res = GetGlyphOutlineW(hdc, glyph, GGO_METRICS|GGO_GLYPH_INDEX, &gm, 0, 0, &mat);
+ } , {
+ res = GetGlyphOutlineA(hdc, glyph, GGO_METRICS|GGO_GLYPH_INDEX, &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) {
+ return glyph_metrics_t(gm.gmptGlyphOrigin.x, -gm.gmptGlyphOrigin.y,
+ (int)gm.gmBlackBoxX, (int)gm.gmBlackBoxY, gm.gmCellIncX, gm.gmCellIncY);
+ }
+ }
+ return glyph_metrics_t();
+#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.w.tmMaxCharWidth;
+ advance = width;
+ }
+
+ SelectObject(hdc, oldFont);
+ return glyph_metrics_t(0, -tm.w.tmAscent, width, tm.w.tmHeight, advance, 0).transformed(t);
+#endif
+}
+
+QFixed QFontEngineWin::ascent() const
+{
+ return tm.w.tmAscent;
+}
+
+QFixed QFontEngineWin::descent() const
+{
+ return tm.w.tmDescent;
+}
+
+QFixed QFontEngineWin::leading() const
+{
+ return tm.w.tmExternalLeading;
+}
+
+
+QFixed QFontEngineWin::xHeight() const
+{
+ if(x_height >= 0)
+ return x_height;
+ return QFontEngine::xHeight();
+}
+
+QFixed QFontEngineWin::averageCharWidth() const
+{
+ return tm.w.tmAveCharWidth;
+}
+
+qreal QFontEngineWin::maxCharWidth() const
+{
+ return tm.w.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);
+
+
+qreal QFontEngineWin::minLeftBearing() const
+{
+ if (lbearing == SHRT_MIN)
+ minRightBearing(); // calculates both
+
+ return lbearing;
+}
+
+qreal QFontEngineWin::minRightBearing() const
+{
+#ifdef Q_OS_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 = QT_WA_INLINE(tm.w.tmLastChar - tm.w.tmFirstChar, tm.a.tmLastChar - tm.a.tmFirstChar);
+ if (n <= max_font_count) {
+ abc = new ABC[n+1];
+ GetCharABCWidths(hdc, tm.w.tmFirstChar, tm.w.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 {
+ ml = 0;
+ mr = -tm.a.tmOverhang;
+ }
+ 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 = QT_WA_INLINE(tm.w.tmLastChar - tm.w.tmFirstChar, tm.a.tmLastChar - tm.a.tmFirstChar);
+ if (n <= max_font_count) {
+ abc = new ABC[n+1];
+ QT_WA({
+ GetCharABCWidths(hdc, tm.w.tmFirstChar, tm.w.tmLastChar, abc);
+ }, {
+ GetCharABCWidthsA(hdc,tm.a.tmFirstChar,tm.a.tmLastChar,abc);
+ });
+ } else {
+ abc = new ABC[char_table_entries+1];
+ QT_WA({
+ for(int i = 0; i < char_table_entries; i++)
+ GetCharABCWidths(hdc, char_table[i], char_table[i], abc+i);
+ }, {
+ for(int i = 0; i < char_table_entries; i++) {
+ QByteArray w = QString(QChar(char_table[i])).toLocal8Bit();
+ if (w.length() == 1) {
+ uint ch8 = (uchar)w[0];
+ GetCharABCWidthsA(hdc, ch8, ch8, 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 {
+ QT_WA({
+ ABCFLOAT *abc = 0;
+ int n = tm.w.tmLastChar - tm.w.tmFirstChar+1;
+ if (n <= max_font_count) {
+ abc = new ABCFLOAT[n];
+ GetCharABCWidthsFloat(hdc, tm.w.tmFirstChar, tm.w.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<n; i++) {
+ if (abc[i].abcfA + abc[i].abcfB + abc[i].abcfC != 0) {
+ fml = qMin(fml,abc[i].abcfA);
+ fmr = qMin(fmr,abc[i].abcfC);
+ }
+ }
+ ml = int(fml-0.9999);
+ mr = int(fmr-0.9999);
+ delete [] abc;
+ } , {
+ ml = 0;
+ mr = -tm.a.tmOverhang;
+ });
+ }
+ lbearing = ml;
+ rbearing = mr;
+ }
+
+ return rbearing;
+#endif
+}
+
+
+const char *QFontEngineWin::name() const
+{
+ return 0;
+}
+
+bool QFontEngineWin::canRender(const QChar *string, int len)
+{
+ if (symbol) {
+ for (int i = 0; i < len; ++i) {
+ unsigned int uc = getChar(string, i, len);
+ if (getTrueTypeGlyphIndex(cmap, uc) == 0) {
+ if (uc < 0x100) {
+ if (getTrueTypeGlyphIndex(cmap, uc + 0xf000) == 0)
+ return false;
+ } else {
+ return false;
+ }
+ }
+ }
+ } else if (ttf) {
+ for (int i = 0; i < len; ++i) {
+ unsigned int uc = getChar(string, i, len);
+ if (getTrueTypeGlyphIndex(cmap, uc) == 0)
+ return false;
+ }
+ } else {
+ QT_WA({
+ while(len--) {
+ if (tm.w.tmFirstChar > string->unicode() || tm.w.tmLastChar < string->unicode())
+ return false;
+ }
+ }, {
+ while(len--) {
+ if (tm.a.tmFirstChar > string->unicode() || tm.a.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_OS_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_OS_WINCE)
+ QT_WA( {
+ bufferSize = GetGlyphOutlineW(hdc, glyph, glyphFormat, &gMetric, 0, 0, &mat);
+ }, {
+ bufferSize = GetGlyphOutlineA(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_OS_WINCE)
+ QT_WA( {
+ ret = GetGlyphOutlineW(hdc, glyph, glyphFormat, &gMetric, bufferSize,
+ dataBuffer, &mat);
+ }, {
+ ret = GetGlyphOutlineA(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 (offset<int(headerOffset + ttph->cb)) {
+ curve = (TTPOLYCURVE*)((char*)(dataBuffer) + offset);
+ switch (curve->wType) {
+ case TT_PRIM_LINE: {
+ for (int i=0; i<curve->cpfx; ++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; i<curve->cpfx - 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; i<curve->cpfx; ) {
+ 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;
+ QT_WA({
+ hf = CreateFontIndirectW(&lf);
+ }, {
+ LOGFONTA lfa;
+ wa_copy_logfont(&lf, &lfa);
+ hf = CreateFontIndirectA(&lfa);
+ });
+ 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_OS_WINCE)
+ if(tm.w.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 <qdebug.h>
+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.w.tmItalic && !(macStyle & 2))
+ synthesized_flags = SynthesizedItalic;
+ if (fontDef.stretch != 100 && ttf)
+ synthesized_flags |= SynthesizedStretch;
+ if (tm.w.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;
+ QT_WA({
+ hf = CreateFontIndirectW(&lf);
+ }, {
+ LOGFONTA lfa;
+ wa_copy_logfont(&lf, &lfa);
+ hf = CreateFontIndirectA(&lfa);
+ });
+ HDC hdc = shared_dc();
+ HGDIOBJ oldfont = SelectObject(hdc, hf);
+#if defined(Q_OS_WINCE)
+ OUTLINETEXTMETRICW *otm = getOutlineTextMetric(hdc);
+#else
+ OUTLINETEXTMETRICA *otm = getOutlineTextMetric(hdc);
+#endif
+ Properties p;
+ p.emSquare = unitsPerEm;
+ p.italicAngle = otm->otmItalicAngle;
+ p.postscriptName = (char *)otm + (int)otm->otmpFamilyName;
+ p.postscriptName += (char *)otm + (int)otm->otmpStyleName;
+#ifndef QT_NO_PRINTER
+ p.postscriptName = QPdf::stripSpecialCharacters(p.postscriptName);
+#endif
+ 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;
+ QT_WA({
+ hf = CreateFontIndirectW(&lf);
+ }, {
+ LOGFONTA lfa;
+ wa_copy_logfont(&lf, &lfa);
+ hf = CreateFontIndirectA(&lfa);
+ });
+ 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<quint32>(tag);
+ *length = GetFontData(hdc, t, 0, buffer, *length);
+ return *length != GDI_ERROR;
+}
+
+#if !defined(CLEARTYPE_QUALITY)
+# define CLEARTYPE_QUALITY 5
+#endif
+
+
+QNativeImage *QFontEngineWin::drawGDIGlyph(HFONT font, glyph_t glyph, int margin,
+ const QTransform &t)
+{
+ 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_OS_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;
+
+ int error = 0;
+ QT_WA( {
+ error = GetGlyphOutlineW(hdc, glyph, ggo_options, &tgm, 0, 0, &mat);
+ }, {
+ error = GetGlyphOutlineA(hdc, glyph, ggo_options, &tgm, 0, 0, &mat);
+ } );
+
+ if (error == 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,
+ ih + 2 * margin,
+ QNativeImage::systemFormat(), true);
+ 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);
+ ExtTextOutW(hdc, 0, 0, options, 0, (LPCWSTR) &glyph, 1, 0);
+ } else
+#endif
+ {
+ ExtTextOutW(hdc, -gx + margin, -gy + margin, options, 0, (LPCWSTR) &glyph, 1, 0);
+ }
+
+ SelectObject(hdc, old_font);
+ return ni;
+}
+
+
+extern bool qt_cleartype_enabled;
+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 = CreateFontIndirectW(&lf);
+ }
+
+ QNativeImage *mask = drawGDIGlyph(font, glyph, 0, xform);
+ 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 dont because some
+ // code paths expects there to be colortables for index8-bit...
+ QVector<QRgb> 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; y<mask->height(); ++y) {
+ uchar *dest = indexed.scanLine(y);
+ if (mask->systemFormat() == QImage::Format_RGB16) {
+ const qint16 *src = (qint16 *) ((const QImage &) mask->image).scanLine(y);
+ for (int x=0; x<mask->width(); ++x) {
+#ifdef Q_OS_WINCE
+ dest[x] = 255 - qGray(src[x]);
+#else
+ dest[x] = 255 - (qt_pow_gamma[qGray(src[x])] * 255. / 2047.);
+#endif
+ }
+ } else {
+ const uint *src = (uint *) ((const QImage &) mask->image).scanLine(y);
+ for (int x=0; x<mask->width(); ++x) {
+#ifdef Q_OS_WINCE
+ 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, 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);
+ 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; y<mask->height(); ++y) {
+ uint *dest = (uint *) rgbMask.scanLine(y);
+ const uint *src = (uint *) source.scanLine(y);
+ for (int x=0; x<mask->width(); ++x) {
+ dest[x] = 0xffffffff - (0x00ffffff & src[x]);
+ }
+ }
+
+ delete mask;
+
+ return rgbMask;
+}
+
+// -------------------------------------- Multi font engine
+
+QFontEngineMultiWin::QFontEngineMultiWin(QFontEngineWin *first, const QStringList &fallbacks)
+ : QFontEngineMulti(fallbacks.size()+1),
+ fallbacks(fallbacks)
+{
+ engines[0] = first;
+ first->ref.ref();
+ fontDef = engines[0]->fontDef;
+}
+
+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<QFontEngineWin *>(engines.at(0))->logfont;
+ HFONT hfont;
+ QT_WA({
+ memcpy(lf.lfFaceName, fam.utf16(), sizeof(TCHAR)*qMin(fam.length()+1,32)); // 32 = Windows hard-coded
+ hfont = CreateFontIndirectW(&lf);
+ } , {
+ // LOGFONTA and LOGFONTW are binary compatible
+ QByteArray lname = fam.toLocal8Bit();
+ memcpy(lf.lfFaceName,lname.data(),
+ qMin(lname.length()+1,32)); // 32 = Windows hard-coded
+ hfont = CreateFontIndirectA((LOGFONTA*)&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;
+}
+
+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..6f37e91bbf
--- /dev/null
+++ b/src/gui/text/qfontengine_win_p.h
@@ -0,0 +1,156 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <QtCore/qconfig.h>
+
+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(QFixed *) 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, int margin, const QTransform &xform);
+
+ int getGlyphIndexes(const QChar *ch, int numChars, QGlyphLayout *glyphs, bool mirrored) const;
+ void getCMap();
+
+ QString _name;
+ HFONT hfont;
+ LOGFONT logfont;
+ uint stockFont : 1;
+ uint useTextOutA : 1;
+ uint ttf : 1;
+ uint hasOutline : 1;
+ union {
+ TEXTMETRICW w;
+ TEXTMETRICA a;
+ } 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);
+
+};
+
+class QFontEngineMultiWin : public QFontEngineMulti
+{
+public:
+ QFontEngineMultiWin(QFontEngineWin *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..0972b2b345
--- /dev/null
+++ b/src/gui/text/qfontengine_x11.cpp
@@ -0,0 +1,1180 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbitmap.h"
+
+// #define FONTENGINE_DEBUG
+
+#include <qapplication.h>
+#include <qbytearray.h>
+#include <qdebug.h>
+#include <qtextcodec.h>
+#include <qthread.h>
+
+#include "qfontdatabase.h"
+#include "qpaintdevice.h"
+#include "qpainter.h"
+#include "qvarlengtharray.h"
+#include "qwidget.h"
+#include "qsettings.h"
+#include "qfile.h"
+
+#include <private/qpaintengine_x11_p.h>
+#include "qfont.h"
+#include "qfont_p.h"
+#include "qfontengine_p.h"
+#include <qhash.h>
+
+#include <private/qpainter_p.h>
+#include <private/qunicodetables_p.h>
+
+#include <private/qt_x11_p.h>
+#include <private/qpixmap_x11_p.h>
+#include "qx11info_x11.h"
+#include "qfontengine_x11_p.h"
+
+#include <limits.h>
+
+#include <ft2build.h>
+#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<int> &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<npaths; i++) {
+ // If we're using xfs, append font paths from /etc/X11/fs/config
+ // can't hurt, and chances are we'll get all fonts that way.
+ if (((font_path[i])[0] != '/') && !xfsconfig_read) {
+ // We're using xfs -> 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).left(1) != QLatin1String("/"))
+ 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<ushort> _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<unsigned short> 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 perfomance
+ 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);
+ 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];
+ } 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) {
+ 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<int>(_fs->ascent, _fs->max_bounds.ascent)
+ + qMin<int>(_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<QFontEngineXLFD *>(this)->fsType = freetype->fsType();
+ } else {
+ QFontEngine::Properties properties = QFontEngine::properties();
+ face_id.index = 0;
+ face_id.filename = "-" + 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<QRgb> 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 = 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;
+ }
+ }
+
+#ifdef FC_HINT_STYLE
+ {
+ int hint_style = 0;
+ if (FcPatternGetInteger (pattern, FC_HINT_STYLE, 0, &hint_style) == FcResultNoMatch)
+ 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..eb00383189
--- /dev/null
+++ b/src/gui/text/qfontengine_x11_p.h
@@ -0,0 +1,177 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <private/qt_x11_p.h>
+
+#include <private/qfontengine_ft_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QFreetypeFace;
+
+// --------------------------------------------------------------------------
+
+class QFontEngineMultiXLFD : public QFontEngineMulti
+{
+public:
+ QFontEngineMultiXLFD(const QFontDef &r, const QList<int> &l, int s);
+ ~QFontEngineMultiXLFD();
+
+ void loadEngine(int at);
+
+private:
+ QList<int> 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(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/qfontengineglyphcache_p.h b/src/gui/text/qfontengineglyphcache_p.h
new file mode 100644
index 0000000000..8589cc68de
--- /dev/null
+++ b/src/gui/text/qfontengineglyphcache_p.h
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QFONTENGINEGLYPHCACHE_P_H
+#define QFONTENGINEGLYPHCACHE_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 <QtCore/qvarlengtharray.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
+
+QT_BEGIN_NAMESPACE
+
+class Q_GUI_EXPORT QFontEngineGlyphCache
+{
+public:
+ QFontEngineGlyphCache(const QTransform &matrix) : m_transform(matrix) { }
+
+ enum Type {
+ Raster_RGBMask,
+ Raster_A8,
+ Raster_Mono
+ };
+
+ virtual ~QFontEngineGlyphCache();
+
+ QTransform m_transform;
+};
+typedef QHash<void *, QList<QFontEngineGlyphCache *> > GlyphPointerHash;
+typedef QHash<int, QList<QFontEngineGlyphCache *> > 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..c1b0bb8934
--- /dev/null
+++ b/src/gui/text/qfontinfo.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QFONTINFO_H
+#define QFONTINFO_H
+
+#include <QtGui/qfont.h>
+
+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:
+ QFontPrivate *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..88d0610f86
--- /dev/null
+++ b/src/gui/text/qfontmetrics.cpp
@@ -0,0 +1,1739 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qfont.h"
+#include "qpaintdevice.h"
+#include "qfontmetrics.h"
+
+#include "qfont_p.h"
+#include "qfontengine_p.h"
+#include <private/qunicodetables_p.h>
+
+#include <math.h>
+
+#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);
+extern int qt_defaultDpi();
+
+/*****************************************************************************
+ QFontMetrics member functions
+ *****************************************************************************/
+
+/*!
+ \class QFontMetrics
+ \reentrant
+
+ \brief The QFontMetrics class provides font metrics information.
+
+ \ingroup multimedia
+ \ingroup shared
+ \ingroup text
+
+ 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)
+{
+ d->ref.ref();
+}
+
+/*!
+ 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;
+ d->ref.ref();
+ }
+
+}
+
+/*!
+ Constructs a copy of \a fm.
+*/
+QFontMetrics::QFontMetrics(const QFontMetrics &fm)
+ : d(fm.d)
+{ d->ref.ref(); }
+
+/*!
+ Destroys the font metrics object and frees all allocated
+ resources.
+*/
+QFontMetrics::~QFontMetrics()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ Assigns the font metrics \a fm.
+*/
+QFontMetrics &QFontMetrics::operator=(const QFontMetrics &fm)
+{
+ qAtomicAssign(d, fm.d);
+ 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() + 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() + engine->ascent() + 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 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
+ glyph_metrics_t gi = engine->boundingBox(glyphs.glyphs[0]);
+ return qRound(gi.x);
+}
+
+/*!
+ 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
+ glyph_metrics_t gi = engine->boundingBox(glyphs.glyphs[0]);
+ return qRound(gi.xoff - gi.x - gi.width);
+}
+
+/*!
+ 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
+{
+ if (len < 0)
+ len = text.length();
+ if (len == 0)
+ return 0;
+
+ QTextEngine layout(text, d);
+ 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. Use charWidth()
+ instead if you aren't looking for the width of isolated
+ characters.
+
+ \sa boundingRect(), charWidth()
+*/
+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);
+ QTextEngine layout(cstr, d);
+ 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();
+
+ QTextEngine layout(text, d);
+ 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 "&amp;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), 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 "&amp;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, 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();
+
+ QTextEngine layout(text, d);
+ 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
+{
+ QStackTextEngine engine(text, QFont(d));
+ 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 multimedia
+ \ingroup shared
+ \ingroup text
+
+ 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)
+{
+ d->ref.ref();
+}
+
+/*!
+ \since 4.2
+
+ Assigns \a other to this object.
+*/
+QFontMetricsF &QFontMetricsF::operator=(const QFontMetrics &other)
+{
+ qAtomicAssign(d, other.d);
+ 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)
+{
+ d->ref.ref();
+}
+
+/*!
+ 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;
+ d->ref.ref();
+ }
+
+}
+
+/*!
+ Constructs a copy of \a fm.
+*/
+QFontMetricsF::QFontMetricsF(const QFontMetricsF &fm)
+ : d(fm.d)
+{ d->ref.ref(); }
+
+/*!
+ Destroys the font metrics object and frees all allocated
+ resources.
+*/
+QFontMetricsF::~QFontMetricsF()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ Assigns the font metrics \a fm to this font metrics object.
+*/
+QFontMetricsF &QFontMetricsF::operator=(const QFontMetricsF &fm)
+{
+ qAtomicAssign(d, fm.d);
+ 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 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
+ glyph_metrics_t gi = engine->boundingBox(glyphs.glyphs[0]);
+ return gi.x.toReal();
+}
+
+/*!
+ 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
+ glyph_metrics_t gi = engine->boundingBox(glyphs.glyphs[0]);
+ return (gi.xoff - gi.x - gi.width).toReal();
+}
+
+/*!
+ 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
+{
+ QTextEngine layout(text, d);
+ layout.ignoreBidi = true;
+ layout.itemize();
+ return layout.width(0, text.length()).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. Use charWidth()
+ instead if you aren't looking for the width of isolated
+ characters.
+
+ \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();
+
+ QTextEngine layout(text, d);
+ 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 "&amp;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), 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 "&amp;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, 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();
+
+ QTextEngine layout(text, d);
+ 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
+{
+ QStackTextEngine engine(text, QFont(d));
+ 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..95f71dfa4b
--- /dev/null
+++ b/src/gui/text/qfontmetrics.h
@@ -0,0 +1,197 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QFONTMETRICS_H
+#define QFONTMETRICS_H
+
+#include <QtGui/qfont.h>
+#ifndef QT_INCLUDE_COMPAT
+#include <QtCore/qrect.h>
+#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 &);
+
+ 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;
+
+ int leftBearing(QChar) const;
+ int rightBearing(QChar) const;
+ int width(const QString &, int len = -1) 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;
+
+ QFontPrivate *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 &);
+
+ 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;
+
+ 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:
+ QFontPrivate *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..0d1a8846ef
--- /dev/null
+++ b/src/gui/text/qfontsubset.cpp
@@ -0,0 +1,1743 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <qdebug.h>
+#include "qfontsubset_p.h"
+#include <qendian.h>
+#include <qpainterpath.h>
+#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 <ft2build.h>
+#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<const QFontEngineFT *>(engine);
+ return ft->non_locked_face();
+ } else
+#endif
+ if (engine->type() == QFontEngine::XLFD) {
+ const QFontEngineXLFD *xlfd = static_cast<const QFontEngineXLFD *>(engine);
+ return xlfd->non_locked_face();
+ }
+#endif
+#ifdef Q_WS_QWS
+ if (engine->type() == QFontEngine::Freetype) {
+ const QFontEngineFT *ft = static_cast<const QFontEngineFT *>(engine);
+ return ft->non_locked_face();
+ }
+#endif
+ return 0;
+}
+#endif
+
+QByteArray QFontSubset::glyphName(unsigned int glyph, const QVector<int> 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<QFontEngineXLFD *>(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<QFontEngineXLFD *>(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<int> QFontSubset::getReverseMap() const
+{
+ QVector<int> 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<int> 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> <FFFF>\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<QTtfTable> generateGlyphTables(qttf_font_tables &tables, const QList<QTtfGlyph> &_glyphs);
+
+static QByteArray bindFont(const QList<QTtfTable>& _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<quint32>(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 optimised 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<QTtfNameRecord> &name);
+
+static QTtfTable generateName(const qttf_name_table &name)
+{
+ QList<QTtfNameRecord> 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<QTtfNameRecord> &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<TTF_POINT> *points, QList<int> *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 optimise 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<TTF_POINT> &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<TTF_POINT> *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<TTF_POINT> &points, const QList<int> &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<TTF_POINT> points;
+ QList<int> 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<QTtfTable> generateGlyphTables(qttf_font_tables &tables, const QList<QTtfGlyph> &_glyphs)
+{
+ const int max_size_small = 65536*2;
+ QList<QTtfGlyph> 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<QTtfTable> 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<QTtfTable>& _tables)
+{
+ QList<QTtfTable> 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<QTtfGlyph> 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<QTtfTable> 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<int> 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 " << (int)fontEngine->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<int> 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..3106ba83a6
--- /dev/null
+++ b/src/gui/text/qfontsubset_p.h
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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<int> getReverseMap() const;
+ QByteArray glyphName(unsigned int glyph, const QVector<int> reverseMap) const;
+
+ static QByteArray glyphName(unsigned short unicode, bool symbol);
+
+ int addGlyph(int index);
+ const int object_id;
+ bool noEmbed;
+ QFontEngine *fontEngine;
+ QList<int> glyph_indices;
+ mutable int downloaded_glyphs;
+ mutable bool standard_font;
+ int nGlyphs() const { return glyph_indices.size(); }
+ mutable QFixed emSquare;
+ mutable QVector<QFixed> 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..c3ce685ba6
--- /dev/null
+++ b/src/gui/text/qfragmentmap.cpp
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <private/qtools_p.h>
+
+#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..737a71775d
--- /dev/null
+++ b/src/gui/text/qfragmentmap_p.h
@@ -0,0 +1,872 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <stdlib.h>
+#include <private/qtools_p.h>
+
+QT_BEGIN_NAMESPACE
+
+
+template <int N = 1>
+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 Fragment>
+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;
+ }
+
+ 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 <class Fragment>
+QFragmentMapData<Fragment>::QFragmentMapData()
+{
+ init();
+}
+
+template <class Fragment>
+void QFragmentMapData<Fragment>::init()
+{
+ fragments = (Fragment *)malloc(64*fragmentSize);
+ 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;
+ head->allocated = 64;
+ // mark all items to the right as unused
+ F(head->freelist).right = 0;
+}
+
+template <class Fragment>
+QFragmentMapData<Fragment>::~QFragmentMapData()
+{
+ free(head);
+}
+
+template <class Fragment>
+uint QFragmentMapData<Fragment>::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);
+ fragments = (Fragment *)realloc(fragments, needed);
+ 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 <class Fragment>
+void QFragmentMapData<Fragment>::freeFragment(uint i)
+{
+ F(i).right = head->freelist;
+ head->freelist = i;
+
+ --head->node_count;
+}
+
+
+template <class Fragment>
+uint QFragmentMapData<Fragment>::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 <class Fragment>
+uint QFragmentMapData<Fragment>::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 <class Fragment>
+void QFragmentMapData<Fragment>::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 <class Fragment>
+void QFragmentMapData<Fragment>::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 <class Fragment>
+void QFragmentMapData<Fragment>::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 <class Fragment>
+uint QFragmentMapData<Fragment>::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 <class Fragment>
+uint QFragmentMapData<Fragment>::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 <class Fragment>
+uint QFragmentMapData<Fragment>::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 <class Fragment>
+int QFragmentMapData<Fragment>::length(uint field) const {
+ uint root = this->root();
+ return root ? sizeLeft(root, field) + size(root, field) + sizeRight(root, field) : 0;
+}
+
+
+template <class Fragment> // 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()
+ {
+ for (Iterator it = begin(); !it.atEnd(); ++it)
+ it.value()->free();
+ }
+
+ inline void clear() {
+ for (Iterator it = begin(); !it.atEnd(); ++it)
+ it.value()->free();
+ ::free(data.head);
+ 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 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<Fragment> data;
+
+ QFragmentMap(const QFragmentMap& m);
+ QFragmentMap& operator= (const QFragmentMap& m);
+};
+
+QT_END_NAMESPACE
+
+#endif // QFRAGMENTMAP_P_H
diff --git a/src/gui/text/qpfutil.cpp b/src/gui/text/qpfutil.cpp
new file mode 100644
index 0000000000..6fba213c48
--- /dev/null
+++ b/src/gui/text/qpfutil.cpp
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+static 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/qsyntaxhighlighter.cpp b/src/gui/text/qsyntaxhighlighter.cpp
new file mode 100644
index 0000000000..87648d5177
--- /dev/null
+++ b/src/gui/text/qsyntaxhighlighter.cpp
@@ -0,0 +1,618 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsyntaxhighlighter.h"
+
+#ifndef QT_NO_SYNTAXHIGHLIGHTER
+#include <private/qobject_p.h>
+#include <qtextdocument.h>
+#include <private/qtextdocument_p.h>
+#include <qtextlayout.h>
+#include <qpointer.h>
+#include <qtextobject.h>
+#include <qtextcursor.h>
+#include <qdebug.h>
+#include <qtextedit.h>
+#include <qtimer.h>
+
+QT_BEGIN_NAMESPACE
+
+class QSyntaxHighlighterPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QSyntaxHighlighter)
+public:
+ inline QSyntaxHighlighterPrivate() : rehighlightPending(false) {}
+
+ QPointer<QTextDocument> doc;
+
+ void _q_reformatBlocks(int from, int charsRemoved, int charsAdded);
+ void reformatBlock(QTextBlock block);
+
+ inline void _q_delayedRehighlight() {
+ if (!rehighlightPending)
+ return;
+ rehighlightPending = false;
+ q_func()->rehighlight();
+ return;
+ }
+
+ void applyFormatChanges();
+ QVector<QTextCharFormat> formatChanges;
+ QTextBlock currentBlock;
+ bool rehighlightPending;
+};
+
+void QSyntaxHighlighterPrivate::applyFormatChanges()
+{
+ QTextLayout *layout = currentBlock.layout();
+
+ QList<QTextLayout::FormatRange> ranges = layout->additionalFormats();
+
+ const int preeditAreaStart = layout->preeditAreaPosition();
+ const int preeditAreaLength = layout->preeditAreaText().length();
+
+ QList<QTextLayout::FormatRange>::Iterator it = ranges.begin();
+ while (it != ranges.end()) {
+ if (it->start >= preeditAreaStart
+ && it->start + it->length <= preeditAreaStart + preeditAreaLength)
+ ++it;
+ else
+ it = ranges.erase(it);
+ }
+
+ QTextCharFormat emptyFormat;
+
+ QTextLayout::FormatRange r;
+ r.start = r.length = -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 (r.start >= preeditAreaStart) {
+ r.start += preeditAreaLength;
+ } else if (r.start + r.length >= preeditAreaStart) {
+ r.length += preeditAreaLength;
+ }
+
+ ranges << r;
+ r.start = r.length = -1;
+ }
+
+ if (r.start != -1) {
+ r.length = formatChanges.count() - r.start;
+
+ if (r.start >= preeditAreaStart) {
+ r.start += preeditAreaLength;
+ } else if (r.start + r.length >= preeditAreaStart) {
+ r.length += preeditAreaLength;
+ }
+
+ ranges << r;
+ }
+
+ layout->setAdditionalFormats(ranges);
+}
+
+void QSyntaxHighlighterPrivate::_q_reformatBlocks(int from, int charsRemoved, int charsAdded)
+{
+ Q_UNUSED(charsRemoved);
+ rehighlightPending = false;
+
+ QTextBlock block = doc->findBlock(from);
+ if (!block.isValid())
+ return;
+
+ int endPosition;
+ QTextBlock lastBlock = doc->findBlock(from + charsAdded);
+ 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(QTextBlock block)
+{
+ Q_Q(QSyntaxHighlighter);
+
+ Q_ASSERT_X(!currentBlock.isValid(), "QSyntaxHighlighter::reformatBlock()", "reFormatBlock() called recursively");
+
+ currentBlock = block;
+ QTextBlock previous = block.previous();
+
+ formatChanges.fill(QTextCharFormat(), block.length() - 1);
+ q->highlightBlock(block.text());
+ applyFormatChanges();
+
+ doc->markContentsDirty(block.position(), block.length());
+
+ 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 text
+
+ 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)));
+ QTimer::singleShot(0, this, SLOT(_q_delayedRehighlight()));
+ d->rehighlightPending = true;
+ }
+}
+
+/*!
+ Returns the QTextDocument on which this syntax highlighter is
+ installed.
+*/
+QTextDocument *QSyntaxHighlighter::document() const
+{
+ Q_D(const QSyntaxHighlighter);
+ return d->doc;
+}
+
+/*!
+ \since 4.2
+
+ Redoes the highlighting of the whole document.
+*/
+void QSyntaxHighlighter::rehighlight()
+{
+ Q_D(QSyntaxHighlighter);
+ if (!d->doc)
+ return;
+
+ disconnect(d->doc, SIGNAL(contentsChange(int,int,int)),
+ this, SLOT(_q_reformatBlocks(int,int,int)));
+ QTextCursor cursor(d->doc);
+ cursor.beginEditBlock();
+ cursor.movePosition(QTextCursor::End);
+ d->_q_reformatBlocks(0, 0, cursor.position());
+ cursor.endEditBlock();
+ connect(d->doc, SIGNAL(contentsChange(int,int,int)),
+ this, SLOT(_q_reformatBlocks(int,int,int)));
+}
+
+/*!
+ \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..481dfd4f77
--- /dev/null
+++ b/src/gui/text/qsyntaxhighlighter.h
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSYNTAXHIGHLIGHTER_H
+#define QSYNTAXHIGHLIGHTER_H
+
+#include <QtCore/qglobal.h>
+
+#ifndef QT_NO_SYNTAXHIGHLIGHTER
+
+#include <QtCore/qobject.h>
+#include <QtGui/qtextobject.h>
+
+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();
+
+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..f3d025c77a
--- /dev/null
+++ b/src/gui/text/qtextcontrol.cpp
@@ -0,0 +1,2981 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtextcontrol_p.h"
+#include "qtextcontrol_p_p.h"
+
+#ifndef QT_NO_TEXTCONTROL
+
+#include <qfont.h>
+#include <qpainter.h>
+#include <qevent.h>
+#include <qdebug.h>
+#include <qmime.h>
+#include <qdrag.h>
+#include <qclipboard.h>
+#include <qmenu.h>
+#include <qstyle.h>
+#include <qtimer.h>
+#include "private/qtextdocumentlayout_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 <qtextformat.h>
+#include <qdatetime.h>
+#include <qbuffer.h>
+#include <qapplication.h>
+#include <limits.h>
+#include <qtexttable.h>
+#include <qvariant.h>
+#include <qurl.h>
+#include <qdesktopservices.h>
+#include <qinputcontext.h>
+#include <qtooltip.h>
+#include <qstyleoption.h>
+#include <QtGui/qlineedit.h>
+
+#ifndef QT_NO_SHORTCUT
+#include "private/qapplication_p.h"
+#include "private/qshortcutmap_p.h"
+#include <qkeysequence.h>
+#define ACCEL_KEY(k) (!qApp->d_func()->shortcutMap.hasShortcutForKeySequence(k) ? QLatin1String("\t") + QString(QKeySequence(k)) : QString())
+#else
+#define ACCEL_KEY(k) QString()
+#endif
+
+QT_BEGIN_NAMESPACE
+
+#ifndef QT_NO_CONTEXTMENU
+#if defined(Q_WS_WIN)
+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),
+#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),
+ openExternalLinks(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();
+
+ if (moved) {
+ if (cursor.position() != oldCursorPos)
+ emit q->cursorPositionChanged();
+ emit q->microFocusChanged();
+ }
+#ifdef QT_KEYPAD_NAVIGATION
+ else if (QApplication::keypadNavigationEnabled()
+ && (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down)) {
+ return false;
+ }
+#endif
+
+ 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);
+
+ QWidget *parentWidget = qobject_cast<QWidget*>(q->parent());
+ if (parentWidget) {
+ QTextOption opt = doc->defaultTextOption();
+ opt.setTextDirection(parentWidget->layoutDirection());
+ doc->setDefaultTextOption(opt);
+ }
+ 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(contentsChanged()), q, SIGNAL(textChanged()));
+ 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);
+
+ // avoid multiple textChanged() signals being emitted
+ QObject::disconnect(doc, SIGNAL(contentsChanged()), q, SIGNAL(textChanged()));
+
+ 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);
+
+ QObject::connect(doc, SIGNAL(contentsChanged()), q, SIGNAL(textChanged()));
+ 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;
+ if (interactionFlags & Qt::TextEditable)
+ actions |= Qt::MoveAction;
+ Qt::DropAction action = drag->exec(actions, Qt::MoveAction);
+
+ 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();
+ d->doc->undo(&d->cursor);
+ ensureCursorVisible();
+}
+
+void QTextControl::redo()
+{
+ Q_D(QTextControl);
+ d->repaintSelection();
+ d->doc->redo(&d->cursor);
+ 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()
+{
+ const QMimeData *md = QApplication::clipboard()->mimeData();
+ 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)
+ 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<QGraphicsSceneEvent *>(e);
+ d->contextWidget = ev->widget();
+ break;
+ }
+#endif // QT_NO_GRAPHICSVIEW
+ default: break;
+ };
+ }
+
+ switch (e->type()) {
+ case QEvent::KeyPress:
+ d->keyPressEvent(static_cast<QKeyEvent *>(e));
+ break;
+ case QEvent::MouseButtonPress: {
+ QMouseEvent *ev = static_cast<QMouseEvent *>(e);
+ d->mousePressEvent(ev->button(), matrix.map(ev->pos()), ev->modifiers(),
+ ev->buttons(), ev->globalPos());
+ break; }
+ case QEvent::MouseMove: {
+ QMouseEvent *ev = static_cast<QMouseEvent *>(e);
+ d->mouseMoveEvent(ev->buttons(), matrix.map(ev->pos()));
+ break; }
+ case QEvent::MouseButtonRelease: {
+ QMouseEvent *ev = static_cast<QMouseEvent *>(e);
+ d->mouseReleaseEvent(ev->button(), matrix.map(ev->pos()));
+ break; }
+ case QEvent::MouseButtonDblClick: {
+ QMouseEvent *ev = static_cast<QMouseEvent *>(e);
+ d->mouseDoubleClickEvent(e, ev->button(), matrix.map(ev->pos()));
+ break; }
+ case QEvent::InputMethod:
+ d->inputMethodEvent(static_cast<QInputMethodEvent *>(e));
+ break;
+#ifndef QT_NO_CONTEXTMENU
+ case QEvent::ContextMenu: {
+ QContextMenuEvent *ev = static_cast<QContextMenuEvent *>(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<QFocusEvent *>(e));
+ break;
+
+ case QEvent::EnabledChange:
+ d->isEnabled = e->isAccepted();
+ break;
+
+#ifndef QT_NO_TOOLTIP
+ case QEvent::ToolTip: {
+ QHelpEvent *ev = static_cast<QHelpEvent *>(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<QDragEnterEvent *>(e);
+ if (d->dragEnterEvent(e, ev->mimeData()))
+ ev->acceptProposedAction();
+ break;
+ }
+ case QEvent::DragLeave:
+ d->dragLeaveEvent();
+ break;
+ case QEvent::DragMove: {
+ QDragMoveEvent *ev = static_cast<QDragMoveEvent *>(e);
+ if (d->dragMoveEvent(e, ev->mimeData(), matrix.map(ev->pos())))
+ ev->acceptProposedAction();
+ break;
+ }
+ case QEvent::Drop: {
+ QDropEvent *ev = static_cast<QDropEvent *>(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<QGraphicsSceneMouseEvent *>(e);
+ d->mousePressEvent(ev->button(), matrix.map(ev->pos()), ev->modifiers(), ev->buttons(),
+ ev->screenPos());
+ break; }
+ case QEvent::GraphicsSceneMouseMove: {
+ QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e);
+ d->mouseMoveEvent(ev->buttons(), matrix.map(ev->pos()));
+ break; }
+ case QEvent::GraphicsSceneMouseRelease: {
+ QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e);
+ d->mouseReleaseEvent(ev->button(), matrix.map(ev->pos()));
+ break; }
+ case QEvent::GraphicsSceneMouseDoubleClick: {
+ QGraphicsSceneMouseEvent *ev = static_cast<QGraphicsSceneMouseEvent *>(e);
+ d->mouseDoubleClickEvent(e, ev->button(), matrix.map(ev->pos()));
+ break; }
+ case QEvent::GraphicsSceneContextMenu: {
+ QGraphicsSceneContextMenuEvent *ev = static_cast<QGraphicsSceneContextMenuEvent *>(e);
+ d->contextMenuEvent(ev->screenPos(), matrix.map(ev->pos()), contextWidget);
+ break; }
+
+ case QEvent::GraphicsSceneHoverMove: {
+ QGraphicsSceneHoverEvent *ev = static_cast<QGraphicsSceneHoverEvent *>(e);
+ d->mouseMoveEvent(Qt::NoButton, matrix.map(ev->pos()));
+ break; }
+
+ case QEvent::GraphicsSceneDragEnter: {
+ QGraphicsSceneDragDropEvent *ev = static_cast<QGraphicsSceneDragDropEvent *>(e);
+ if (d->dragEnterEvent(e, ev->mimeData()))
+ ev->acceptProposedAction();
+ break; }
+ case QEvent::GraphicsSceneDragLeave:
+ d->dragLeaveEvent();
+ break;
+ case QEvent::GraphicsSceneDragMove: {
+ QGraphicsSceneDragDropEvent *ev = static_cast<QGraphicsSceneDragDropEvent *>(e);
+ if (d->dragMoveEvent(e, ev->mimeData(), matrix.map(ev->pos())))
+ ev->acceptProposedAction();
+ break; }
+ case QEvent::GraphicsSceneDrop: {
+ QGraphicsSceneDragDropEvent *ev = static_cast<QGraphicsSceneDragDropEvent *>(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<QKeyEvent *>(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;
+ case QEvent::LayoutDirectionChange: {
+ if (contextWidget) {
+ QTextOption opt = document()->defaultTextOption();
+ opt.setTextDirection(contextWidget->layoutDirection());
+ document()->setDefaultTextOption(opt);
+ }
+ }
+ // FALL THROUGH
+ 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 {
+ cursor.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) {
+ q->paste();
+ }
+#endif
+ else if (e == QKeySequence::Delete) {
+ cursor.deleteChar();
+ }
+ else if (e == QKeySequence::DeleteEndOfWord) {
+ cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor);
+ cursor.removeSelectedText();
+ }
+ else if (e == QKeySequence::DeleteStartOfWord) {
+ 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<QTextEdit *>(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);
+ emit q->updateRequest(q->blockBoundingRect(block));
+}
+
+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<QTextFrame *> children = frame->childFrames();
+
+ const QList<QTextFrame *>::ConstIterator firstFrame = qLowerBound(children.constBegin(), children.constEnd(),
+ cursor.selectionStart(), firstFramePosLessThanCursorPos);
+ const QList<QTextFrame *>::ConstIterator lastFrame = qUpperBound(children.constBegin(), children.constEnd(),
+ cursor.selectionEnd(), cursorPosLessThanLastFramePos);
+ for (QList<QTextFrame *>::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(Qt::MouseButton button, const QPointF &pos, Qt::KeyboardModifiers modifiers,
+ Qt::MouseButtons buttons, const QPoint &globalPos)
+{
+ Q_Q(QTextControl);
+
+ if (interactionFlags & Qt::LinksAccessibleByMouse) {
+ anchorOnMousePress = q->anchorAt(pos);
+
+ if (cursorIsFocusIndicator) {
+ cursorIsFocusIndicator = false;
+ repaintSelection();
+ cursor.clearSelection();
+ }
+ }
+ if (!(button & Qt::LeftButton))
+ return;
+
+ if (!((interactionFlags & Qt::TextSelectableByMouse) || (interactionFlags & Qt::TextEditable)))
+ return;
+
+ cursorIsFocusIndicator = false;
+ const QTextCursor oldSelection = cursor;
+ const int oldCursorPos = cursor.position();
+
+ mousePressed = true;
+#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)
+ return;
+
+#if !defined(QT_NO_IM)
+ QTextLayout *layout = cursor.block().layout();
+ if (contextWidget && layout && !layout->preeditAreaText().isEmpty()) {
+ QInputContext *ctx = inputContext();
+ if (ctx) {
+ QMouseEvent ev(QEvent::MouseButtonPress, contextWidget->mapFromGlobal(globalPos), globalPos,
+ button, buttons, modifiers);
+ ctx->mouseHandler(cursorPos - cursor.position(), &ev);
+ }
+ if (!layout->preeditAreaText().isEmpty())
+ return;
+ }
+#endif
+ if (modifiers == Qt::ShiftModifier) {
+ if (selectedBlockOnTrippleClick.hasSelection())
+ extendBlockwiseSelection(cursorPos);
+ else if (selectedWordOnDoubleClick.hasSelection())
+ extendWordwiseSelection(cursorPos, pos.x());
+ else
+ setCursorPosition(cursorPos, QTextCursor::KeepAnchor);
+ } else {
+
+ if (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(Qt::MouseButtons buttons, const QPointF &mousePos)
+{
+ Q_Q(QTextControl);
+
+ if (interactionFlags & Qt::LinksAccessibleByMouse) {
+ QString anchor = q->anchorAt(mousePos);
+ if (anchor != highlightedAnchor) {
+ highlightedAnchor = anchor;
+ emit q->linkHovered(anchor);
+ }
+ }
+
+ if (!(buttons & Qt::LeftButton))
+ return;
+
+ if (!((interactionFlags & Qt::TextSelectableByMouse) || (interactionFlags & Qt::TextEditable)))
+ return;
+
+ if (!(mousePressed
+ || 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;
+ }
+ const qreal mouseX = qreal(mousePos.x());
+
+#if !defined(QT_NO_IM)
+ QTextLayout *layout = cursor.block().layout();
+ if (layout && !layout->preeditAreaText().isEmpty())
+ return;
+#endif
+
+ int newCursorPos = q->hitTest(mousePos, Qt::FuzzyHit);
+ if (newCursorPos == -1)
+ return;
+
+ 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();
+ } else {
+ //emit q->visibilityRequest(QRectF(mousePos, QSizeF(1, 1)));
+ if (cursor.position() != oldCursorPos)
+ emit q->cursorPositionChanged();
+ }
+ selectionChanged(true);
+ repaintOldAndNewSelection(oldSelection);
+}
+
+void QTextControlPrivate::mouseReleaseEvent(Qt::MouseButton button, const QPointF &pos)
+{
+ Q_Q(QTextControl);
+
+ 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
+ if (interactionFlags & Qt::TextSelectableByMouse) {
+ 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)
+{
+ Q_Q(QTextControl);
+ if (button != Qt::LeftButton
+ || !(interactionFlags & Qt::TextSelectableByMouse)) {
+ e->ignore();
+ return;
+ }
+#if !defined(QT_NO_IM)
+ QTextLayout *layout = cursor.block().layout();
+ if (layout && !layout->preeditAreaText().isEmpty())
+ return;
+#endif
+
+#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(qApp->doubleClickInterval(), q);
+ if (doEmit) {
+ selectionChanged();
+#ifndef QT_NO_CLIPBOARD
+ setClipboardSelection();
+#endif
+ emit q->cursorPositionChanged();
+ }
+}
+
+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->exec(screenPos);
+ delete menu;
+#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)
+{
+ if (!(interactionFlags & Qt::TextEditable) || cursor.isNull()) {
+ e->ignore();
+ return;
+ }
+ cursor.beginEditBlock();
+
+ 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());
+ }
+
+ QTextBlock block = cursor.block();
+ QTextLayout *layout = block.layout();
+ layout->setPreeditArea(cursor.position() - block.position(), e->preeditString());
+ QList<QTextLayout::FormatRange> overrides;
+ 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<QTextFormat>(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();
+}
+
+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.selectionEnd() - block.position());
+ case Qt::ImSurroundingText:
+ return QVariant(block.text());
+ case Qt::ImCurrentSelection:
+ return QVariant(d->cursor.selectedText());
+ 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)) {
+#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<QAction *> imActions = qic->actions();
+ for (int i = 0; i < imActions.size(); ++i)
+ menu->addAction(imActions.at(i));
+ }
+ }
+#endif
+
+#if defined(Q_WS_WIN)
+ 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<QTextEdit::ExtraSelection> &selections)
+{
+ Q_D(QTextControl);
+
+ QHash<int, int> 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<int, int>::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<int, int>::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<QTextEdit::ExtraSelection> QTextControl::extraSelections() const
+{
+ Q_D(const QTextControl);
+ QList<QTextEdit::ExtraSelection> 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;
+}
+
+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;
+}
+
+#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<QTextDocument *>(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;
+ }
+ 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("<meta name=\"qrichtext\" content=\"1\" />"));
+ 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 = qVariantValue<QTextFormat>(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<QAction *>(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<QTextEdit *>(editWidget)) {
+ edit->insertPlainText(str);
+ return;
+ }
+#endif
+ if (QTextControl *control = qobject_cast<QTextControl *>(editWidget)) {
+ control->insertPlainText(str);
+ }
+#ifndef QT_NO_LINEEDIT
+ if (QLineEdit *edit = qobject_cast<QLineEdit *>(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<QTextEditMimeData *>(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..e50540aa34
--- /dev/null
+++ b/src/gui/text/qtextcontrol_p.h
@@ -0,0 +1,303 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <QtGui/qtextdocument.h>
+#include <QtGui/qtextoption.h>
+#include <QtGui/qtextcursor.h>
+#include <QtGui/qtextformat.h>
+#include <QtGui/qtextedit.h>
+#include <QtGui/qmenu.h>
+#include <QtCore/qrect.h>
+#include <QtGui/qabstracttextdocumentlayout.h>
+#include <QtGui/qtextdocumentfragment.h>
+
+#ifdef QT3_SUPPORT
+#include <QtGui/qtextobject.h>
+#include <QtGui/qtextlayout.h>
+#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)
+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<QTextEdit::ExtraSelection> &selections);
+ QList<QTextEdit::ExtraSelection> extraSelections() const;
+#endif
+
+ void setTextWidth(qreal width);
+ qreal textWidth() const;
+ QSizeF size() const;
+
+ void setOpenExternalLinks(bool open);
+ bool openExternalLinks() const;
+
+ void moveCursor(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor);
+
+ bool canPaste() const;
+
+ void setCursorIsFocusIndicator(bool b);
+ bool cursorIsFocusIndicator() const;
+
+#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();
+#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..06955ce9ac
--- /dev/null
+++ b/src/gui/text/qtextcontrol_p_p.h
@@ -0,0 +1,219 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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(Qt::MouseButton button, const QPointF &pos,
+ Qt::KeyboardModifiers modifiers,
+ Qt::MouseButtons buttons,
+ const QPoint &globalPos = QPoint());
+ void mouseMoveEvent(Qt::MouseButtons buttons, const QPointF &pos);
+ void mouseReleaseEvent(Qt::MouseButton button, const QPointF &pos);
+ void mouseDoubleClickEvent(QEvent *e, Qt::MouseButton button, const QPointF &pos);
+ 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 mousePressed;
+
+ bool mightStartDrag;
+ QPoint dragStartPos;
+ QPointer<QWidget> 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<QAbstractTextDocumentLayout::Selection> extraSelections;
+
+ QPalette palette;
+ bool hasFocus;
+#ifdef QT_KEYPAD_NAVIGATION
+ bool hasEditFocus;
+#endif
+ bool isEnabled;
+
+ QString highlightedAnchor; // Anchor below cursor
+ QString anchorOnMousePress;
+ bool hadSelectionOnMousePress;
+
+ bool openExternalLinks;
+
+ 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..c327b9f2c9
--- /dev/null
+++ b/src/gui/text/qtextcursor.cpp
@@ -0,0 +1,2420 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <qtextlayout.h>
+#include <qdebug.h>
+
+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)
+{
+ 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;
+ 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)) {
+ 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 && 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;
+ priv->beginEditBlock();
+ 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) {
+ int startRow, startCol, numRows, numCols;
+ selectedTableCells(&startRow, &numRows, &startCol, &numCols);
+ clearCells(table, startRow, startCol, numRows, numCols, op);
+ } else {
+ priv->remove(pos1, pos2-pos1, op);
+ }
+
+ adjusted_anchor = anchor = position;
+ priv->endEditBlock();
+}
+
+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<QTextFrame *> positionChain;
+ QList<QTextFrame *> 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<QTextTable *>(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<QTextTable *>(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 (op >= QTextCursor::Left && op <= QTextCursor::WordRight
+ && blockIt.blockFormat().layoutDirection() == 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<QTextTable *>(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<QTextTable *>(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<QTextTable *>(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<QTextTable *>(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<QTextTable *>(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<QTextTable *>(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 text
+ \ingroup shared
+ \mainclass
+
+ 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 with the cursor's position() being \e
+ between any two characters (or at the very beginning or very end
+ of the document). 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 after 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 usually after every character in the text. 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.
+ */
+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()
+*/
+int QTextCursor::position() const
+{
+ if (!d || !d->priv)
+ return -1;
+ return d->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;
+}
+
+/*!
+ 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);
+
+ 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 == QLatin1Char('\r')) {
+
+ 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);
+ }
+ 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) {
+ 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) {
+ 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->remove();
+ 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;
+}
+
+/*!
+ 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<QTextList *>(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<QTextList *>(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<QTextTable *>(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;
+
+ 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.
+*/
+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..97af32321e
--- /dev/null
+++ b/src/gui/text/qtextcursor.h
@@ -0,0 +1,232 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTCURSOR_H
+#define QTEXTCURSOR_H
+
+#include <QtCore/qstring.h>
+#include <QtCore/qshareddata.h>
+#include <QtGui/qtextformat.h>
+
+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 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 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<QTextCursorPrivate> d;
+ friend class QTextDocumentFragmentPrivate;
+ friend class QTextCopyHelper;
+};
+
+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..b16af219e7
--- /dev/null
+++ b/src/gui/text/qtextcursor_p.h
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <private/qtextformat_p.h>
+#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;
+ bool visualNavigation;
+};
+
+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..e84b3240ff
--- /dev/null
+++ b/src/gui/text/qtextdocument.cpp
@@ -0,0 +1,2929 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtextdocument.h"
+#include <qtextformat.h>
+#include "qtextdocumentlayout_p.h"
+#include "qtextdocumentfragment.h"
+#include "qtextdocumentfragment_p.h"
+#include "qtexttable.h"
+#include "qtextlist.h"
+#include <qdebug.h>
+#include <qregexp.h>
+#include <qvarlengtharray.h>
+#include <qtextcodec.h>
+#include <qthread.h>
+#include "qtexthtmlparser_p.h"
+#include "qpainter.h"
+#include "qprinter.h"
+#include "qtextedit.h"
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qdir.h>
+#include <qapplication.h>
+#include "qtextcontrol_p.h"
+#include "private/qtextedit_p.h"
+
+#include "qtextdocument_p.h"
+#include <private/qprinter_p.h>
+
+#include <limits.h>
+
+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 <QTextDocument> 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 <?xml ... ?> as for example with xhtml
+ if (text.mid(start, 5) == QLatin1String("<?xml")) {
+ while (start < text.length()) {
+ if (text.at(start) == QLatin1Char('?')
+ && start + 2 < text.length()
+ && text.at(start + 1) == QLatin1Char('>')) {
+ start += 2;
+ break;
+ }
+ ++start;
+ }
+
+ while (start < text.length() && text.at(start).isSpace())
+ ++start;
+ }
+
+ if (text.mid(start, 5).toLower() == QLatin1String("<!doc"))
+ return true;
+ int open = start;
+ while (open < text.length() && text.at(open) != QLatin1Char('<')
+ && text.at(open) != QLatin1Char('\n')) {
+ if (text.at(open) == QLatin1Char('&') && text.mid(open+1,3) == QLatin1String("lt;"))
+ return true; // support desperate attempt of user to see <...>
+ ++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 (!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{>}, 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 <QTextDocument> 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("&lt;");
+ else if (plain.at(i) == QLatin1Char('>'))
+ rich += QLatin1String("&gt;");
+ else if (plain.at(i) == QLatin1Char('&'))
+ rich += QLatin1String("&amp;");
+ 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 <QTextDocument> header file.
+
+ \sa escape(), mightBeRichText()
+*/
+QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode)
+{
+ int col = 0;
+ QString rich;
+ rich += QLatin1String("<p>");
+ 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("<br>\n");
+ else {
+ rich += QLatin1String("</p>\n");
+ while (--c > 1)
+ rich += QLatin1String("<br>\n");
+ rich += QLatin1String("<p>");
+ }
+ 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("&lt;");
+ else if (plain[i] == QLatin1Char('>'))
+ rich += QLatin1String("&gt;");
+ else if (plain[i] == QLatin1Char('&'))
+ rich += QLatin1String("&amp;");
+ else
+ rich += plain[i];
+ ++col;
+ }
+ }
+ if (col != 0)
+ rich += QLatin1String("</p>");
+ return rich;
+}
+
+#ifndef QT_NO_TEXTCODEC
+/*!
+ \internal
+
+ This function is defined in the \c <QTextDocument> 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 text
+ \mainclass
+
+ 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.
+
+ \sa QTextCursor QTextEdit \link richtext.html Rich Text Processing\endlink
+*/
+
+/*!
+ \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);
+ }
+}
+
+/*!
+ \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);
+ if (!d->inContentsChange)
+ d->beginEditBlock();
+ d->documentChange(from, length);
+ if (!d->inContentsChange)
+ d->endEditBlock();
+}
+
+/*!
+ \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<QTextDocumentLayout *>(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.
+*/
+bool QTextDocument::isUndoAvailable() const
+{
+ Q_D(const QTextDocument);
+ return d->isUndoAvailable();
+}
+
+/*!
+ Returns true if redo is available; otherwise returns false.
+*/
+bool QTextDocument::isRedoAvailable() const
+{
+ Q_D(const QTextDocument);
+ return d->isRedoAvailable();
+}
+
+
+/*! \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->undoState;
+}
+
+
+
+/*!
+ 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<QTextDocument *>(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->clear();
+ QTextCursor(this).insertText(text);
+ 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,
+ "<b>bold</b> 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->clear();
+ QTextHtmlImporter(this, html, QTextHtmlImporter::ImportToDocument).import();
+ 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();
+}
+
+extern int qt_defaultDpi();
+
+/*!
+ 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;
+ QTextDocument *clonedDoc = 0;
+ (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<QTextDocument *>(this));
+ clonedDoc = const_cast<QTextDocument *>(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());
+
+ 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->numCopies();
+ } else {
+ docCopies = printer->numCopies();
+ 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 (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)
+ goto UserCanceled;
+ 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();
+ }
+
+UserCanceled:
+ delete clonedDoc;
+}
+#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<QTextDocument *>(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<QTextDocument *>(parent());
+ if (doc) {
+ r = doc->loadResource(type, name);
+ }
+#ifndef QT_NO_TEXTEDIT
+ else if (QTextEdit *edit = qobject_cast<QTextEdit *>(parent())) {
+ QUrl resolvedName = edit->d_func()->resolveUrl(name);
+ r = edit->loadResource(type, resolvedName);
+ }
+#endif
+#ifndef QT_NO_TEXTCONTROL
+ else if (QTextControl *control = qobject_cast<QTextControl *>(parent())) {
+ r = control->loadResource(type, name);
+ }
+#endif
+
+ // 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);
+ }
+ }
+
+ 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<int, QVariant> props = to.properties();
+ for (QMap<int, QVariant>::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("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
+ "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
+ "<html><head><meta name=\"qrichtext\" content=\"1\" />");
+ html.reserve(doc->docHandle()->length());
+
+ fragmentMarkers = (mode == ExportFragment);
+
+ if (!encoding.isEmpty())
+ html += QString::fromLatin1("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\" />").arg(QString::fromAscii(encoding));
+
+ QString title = doc->metaInformation(QTextDocument::DocumentTitle);
+ if (!title.isEmpty())
+ html += QString::fromLatin1("<title>") + title + QString::fromLatin1("</title>");
+ html += QLatin1String("<style type=\"text/css\">\n");
+ html += QLatin1String("p, li { white-space: pre-wrap; }\n");
+ html += QLatin1String("</style>");
+ html += QLatin1String("</head><body");
+
+ if (mode == ExportEntireDocument) {
+ html += QLatin1String(" style=\"");
+
+ emitFontFamily(defaultCharFormat.fontFamily());
+
+ if (defaultCharFormat.hasProperty(QTextFormat::FontPointSize)) {
+ html += QLatin1String(" font-size:");
+ html += QString::number(defaultCharFormat.fontPointSize());
+ html += QLatin1String("pt;");
+ }
+
+ html += QLatin1String(" font-weight:");
+ html += QString::number(defaultCharFormat.fontWeight() * 8);
+ html += QLatin1Char(';');
+
+ html += QLatin1String(" font-style:");
+ html += (defaultCharFormat.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
+ html += QLatin1Char(';');
+
+ // do not set text-decoration on the default font since those values are /always/ propagated
+ // and cannot be turned off with CSS
+
+ html += QLatin1Char('\"');
+
+ const QTextFrameFormat fmt = doc->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("</body></html>");
+ return html;
+}
+
+void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value)
+{
+ html += QLatin1Char(' ');
+ html += QLatin1String(attribute);
+ html += QLatin1String("=\"");
+ html += 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 += QLatin1String("\"");
+}
+
+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:");
+
+ QLatin1Char quote('\'');
+ if (family.contains(quote))
+ quote = QLatin1Char('\"');
+
+ html += quote;
+ html += 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("<a name=\"");
+ html += name;
+ html += QLatin1String("\"></a>");
+ }
+ const QString href = format.anchorHref();
+ if (!href.isEmpty()) {
+ html += QLatin1String("<a href=\"");
+ html += href;
+ html += QLatin1String("\">");
+ closeAnchor = true;
+ }
+ }
+
+ QString txt = fragment.text();
+ const bool isObject = txt.contains(QChar::ObjectReplacementCharacter);
+ const bool isImage = isObject && format.isImageFormat();
+
+ QLatin1String styleTag("<span style=\"");
+ html += styleTag;
+
+ bool attributesEmitted = false;
+ if (!isImage)
+ attributesEmitted = emitCharFormatStyle(format);
+ if (attributesEmitted)
+ html += QLatin1String("\">");
+ else
+ html.chop(qstrlen(styleTag.latin1()));
+
+ if (isObject) {
+ for (int i = 0; isImage && i < txt.length(); ++i) {
+ QTextImageFormat imgFmt = format.toImageFormat();
+
+ html += QLatin1String("<img");
+
+ if (imgFmt.hasProperty(QTextFormat::ImageName))
+ emitAttribute("src", imgFmt.name());
+
+ if (imgFmt.hasProperty(QTextFormat::ImageWidth))
+ emitAttribute("width", QString::number(imgFmt.width()));
+
+ if (imgFmt.hasProperty(QTextFormat::ImageHeight))
+ emitAttribute("height", QString::number(imgFmt.height()));
+
+ if (imgFmt.verticalAlignment() == QTextCharFormat::AlignMiddle)
+ html += QLatin1String(" style=\"vertical-align: middle;\"");
+ else if (imgFmt.verticalAlignment() == QTextCharFormat::AlignTop)
+ html += QLatin1String(" style=\"vertical-align: top;\"");
+
+ if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(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("<br />"); // space on purpose for compatibility with Netscape, Lynx & Co.
+ html += lines.at(i);
+ }
+ }
+
+ if (attributesEmitted)
+ html += QLatin1String("</span>");
+
+ if (closeAnchor)
+ html += QLatin1String("</a>");
+}
+
+static bool isOrderedList(int style)
+{
+ return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha
+ || style == QTextListFormat::ListUpperAlpha;
+}
+
+void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block)
+{
+ QTextBlockFormat format = block.blockFormat();
+ emitAlignment(format.alignment());
+
+ Qt::LayoutDirection dir = format.layoutDirection();
+ if (dir == Qt::LeftToRight) {
+ // assume default to not bloat the html too much
+ // html += QLatin1String(" dir='ltr'");
+ } else {
+ 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 <ul> or appropriate
+ const QTextListFormat format = list->format();
+ const int style = format.style();
+ switch (style) {
+ case QTextListFormat::ListDecimal: html += QLatin1String("<ol"); break;
+ case QTextListFormat::ListDisc: html += QLatin1String("<ul"); break;
+ case QTextListFormat::ListCircle: html += QLatin1String("<ul type=\"circle\""); break;
+ case QTextListFormat::ListSquare: html += QLatin1String("<ul type=\"square\""); break;
+ case QTextListFormat::ListLowerAlpha: html += QLatin1String("<ol type=\"a\""); break;
+ case QTextListFormat::ListUpperAlpha: html += QLatin1String("<ol type=\"A\""); break;
+ default: html += QLatin1String("<ul"); // ### should not happen
+ }
+
+ if (format.hasProperty(QTextFormat::ListIndent)) {
+ html += QLatin1String(" style=\"-qt-list-indent: ");
+ html += QString::number(format.indent());
+ html += QLatin1String(";\"");
+ }
+
+ html += QLatin1Char('>');
+ }
+
+ html += QLatin1String("<li");
+
+ const QTextCharFormat blockFmt = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat();
+ if (!blockFmt.properties().isEmpty()) {
+ html += QLatin1String(" style=\"");
+ emitCharFormatStyle(blockFmt);
+ html += QLatin1Char('\"');
+
+ defaultCharFormat.merge(block.charFormat());
+ }
+ }
+
+ const QTextBlockFormat blockFormat = block.blockFormat();
+ if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
+ html += QLatin1String("<hr");
+
+ QTextLength width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth);
+ if (width.type() != QTextLength::VariableLength)
+ emitTextLength("width", width);
+ else
+ html += QLatin1Char(' ');
+
+ html += QLatin1String("/>");
+ return;
+ }
+
+ const bool pre = blockFormat.nonBreakableLines();
+ if (pre) {
+ if (list)
+ html += QLatin1Char('>');
+ html += QLatin1String("<pre");
+ } else if (!list) {
+ html += QLatin1String("<p");
+ }
+
+ emitBlockAttributes(block);
+
+ html += QLatin1Char('>');
+
+ QTextBlock::Iterator it = block.begin();
+ if (fragmentMarkers && !it.atEnd() && block == doc->begin())
+ html += QLatin1String("<!--StartFragment-->");
+
+ for (; !it.atEnd(); ++it)
+ emitFragment(it.fragment());
+
+ if (fragmentMarkers && block.position() + block.length() == doc->docHandle()->length())
+ html += QLatin1String("<!--EndFragment-->");
+
+ if (pre)
+ html += QLatin1String("</pre>");
+ else if (list)
+ html += QLatin1String("</li>");
+ else
+ html += QLatin1String("</p>");
+
+ if (list) {
+ if (list->itemNumber(block) == list->count() - 1) { // last item? close list
+ if (isOrderedList(list->format().style()))
+ html += QLatin1String("</ol>");
+ else
+ html += QLatin1String("</ul>");
+ }
+ }
+
+ 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<QTextDocument *>(doc->parent()))
+ return findUrlForImage(parent, cacheKey, isPixmap);
+
+ if (doc && doc->docHandle()) {
+ QTextDocumentPrivate *priv = doc->docHandle();
+ QMap<QUrl, QVariant>::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<QImage>(v).cacheKey() == cacheKey)
+ break;
+ }
+
+ if (v.type() == QVariant::Pixmap && isPixmap) {
+ if (qvariant_cast<QPixmap>(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<table");
+
+ if (format.hasProperty(QTextFormat::FrameBorder))
+ emitAttribute("border", QString::number(format.border()));
+
+ emitFrameStyle(format, TableFrame);
+
+ emitAlignment(format.alignment());
+ emitTextLength("width", format.width());
+
+ if (format.hasProperty(QTextFormat::TableCellSpacing))
+ emitAttribute("cellspacing", QString::number(format.cellSpacing()));
+ if (format.hasProperty(QTextFormat::TableCellPadding))
+ emitAttribute("cellpadding", QString::number(format.cellPadding()));
+
+ emitBackgroundAttribute(format);
+
+ html += QLatin1Char('>');
+
+ const int rows = table->rows();
+ const int columns = table->columns();
+
+ QVector<QTextLength> columnWidths = format.columnWidthConstraints();
+ if (columnWidths.isEmpty()) {
+ columnWidths.resize(columns);
+ columnWidths.fill(QTextLength());
+ }
+ Q_ASSERT(columnWidths.count() == columns);
+
+ QVarLengthArray<bool> widthEmittedForColumn(columns);
+ for (int i = 0; i < columns; ++i)
+ widthEmittedForColumn[i] = false;
+
+ const int headerRowCount = qMin(format.headerRowCount(), rows);
+ if (headerRowCount > 0)
+ html += QLatin1String("<thead>");
+
+ for (int row = 0; row < rows; ++row) {
+ html += QLatin1String("\n<tr>");
+
+ 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<td");
+
+ if (!widthEmittedForColumn[col] && cell.columnSpan() == 1) {
+ emitTextLength("width", columnWidths.at(col));
+ widthEmittedForColumn[col] = true;
+ }
+
+ if (cell.columnSpan() > 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("</td>");
+
+ defaultCharFormat = oldDefaultCharFormat;
+ }
+
+ html += QLatin1String("</tr>");
+ if (headerRowCount > 0 && row == headerRowCount - 1)
+ html += QLatin1String("</thead>");
+ }
+
+ html += QLatin1String("</table>");
+}
+
+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<QTextTable *>(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("\n<table");
+ QTextFrameFormat format = f->frameFormat();
+
+ 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 <body> tag
+ if (frameType != RootFrame)
+ emitBackgroundAttribute(format);
+
+ html += QLatin1Char('>');
+ html += QLatin1String("\n<tr>\n<td style=\"border: none;\">");
+ emitFrame(f->begin());
+ html += QLatin1String("</td></tr></table>");
+}
+
+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<QTextFormat> 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<QTextDocumentPrivate *>(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..c33778312e
--- /dev/null
+++ b/src/gui/text/qtextdocument.h
@@ -0,0 +1,298 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTDOCUMENT_H
+#define QTEXTDOCUMENT_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qsize.h>
+#include <QtCore/qrect.h>
+#include <QtGui/qfont.h>
+
+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<typename T> 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 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<QTextFormat> 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);
+
+ 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)
+};
+
+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..320ecbfc4e
--- /dev/null
+++ b/src/gui/text/qtextdocument_p.cpp
@@ -0,0 +1,1600 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <private/qtools_p.h>
+#include <qdebug.h>
+
+#include "qtextdocument_p.h"
+#include "qtextdocument.h"
+#include <qtextformat.h>
+#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 <stdlib.h>
+
+QT_BEGIN_NAMESPACE
+
+#define PMDEBUG if(0) qDebug
+
+/*
+ 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 neccessarily 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),
+ initialBlockCharFormatIndex(-1) // set correctly later in init()
+{
+ editBlock = 0;
+ docChangeFrom = -1;
+
+ undoState = 0;
+
+ lout = 0;
+
+ modified = false;
+ modifiedState = 0;
+
+ undoEnabled = true;
+ inContentsChange = 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()
+{
+ rtFrame = 0;
+ 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);
+ for (int i = 0; i < cursors.count(); ++i) {
+ cursors.at(i)->setPosition(0);
+ cursors.at(i)->currentCharFormat = -1;
+ cursors.at(i)->anchor = 0;
+ cursors.at(i)->adjusted_anchor = 0;
+ }
+
+ QList<QTextCursorPrivate *>oldCursors = cursors;
+ cursors.clear();
+ changedCursors.clear();
+
+ QMap<int, QTextObject *>::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();
+ undoState = 0;
+ truncateUndoStack();
+ text = QString();
+ unreachableCharacterCount = 0;
+ modifiedState = 0;
+ modified = false;
+ formats = QTextFormatCollection();
+ int len = fragments.length();
+ fragments.clear();
+ blocks.clear();
+ cachedResources.clear();
+ delete rtFrame;
+ init();
+ cursors = oldCursors;
+ inContentsChange = true;
+ q->contentsChange(0, len, 0);
+ inContentsChange = false;
+ if (lout)
+ lout->documentChanged(0, len, 0);
+}
+
+QTextDocumentPrivate::~QTextDocumentPrivate()
+{
+ for (int i = 0; i < cursors.count(); ++i)
+ cursors.at(i)->priv = 0;
+ cursors.clear();
+ undoState = 0;
+ undoEnabled = true;
+ truncateUndoStack();
+}
+
+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)
+{
+ // ##### optimise 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<QTextFrame *>(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<QTextBlockGroup *>(objectForFormat(blockFormat));
+ if (group)
+ group->blockInserted(QTextBlock(this, b));
+
+ QTextFrame *frame = qobject_cast<QTextFrame *>(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);
+
+ QTextUndoCommand c = { QTextUndoCommand::BlockInserted, true,
+ 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 : undoState;
+ b = blocks.next(b);
+ if (b) {
+ B = blocks.fragment(b);
+ B->revision = atBlockStart ? oldRevision : undoState;
+ }
+
+ 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());
+
+ beginEditBlock();
+ insert_string(pos, strPos, strLength, format, QTextUndoCommand::MoveCursor);
+ if (undoEnabled) {
+ int b = blocks.findNode(pos);
+ QTextBlockData *B = blocks.fragment(b);
+
+ QTextUndoCommand c = { QTextUndoCommand::Inserted, true,
+ QTextUndoCommand::MoveCursor, format, strPos, pos, { strLength },
+ B->revision };
+ appendUndoItem(c);
+ B->revision = undoState;
+ Q_ASSERT(undoState == undoStack.size());
+ }
+ endEditBlock();
+}
+
+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<QTextFrame *>(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<QTextBlockGroup *>(objectForFormat(blocks.fragment(b)->format));
+ if (group)
+ group->blockRemoved(QTextBlock(this, b));
+
+ QTextFrame *frame = qobject_cast<QTextFrame *>(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<QTextTable *>(frameAt(pos + length - 1))
+ && frameAt(pos + length - 1)->parentFrame() == frameAt(pos));
+
+ Q_ASSERT(startAndEndInSameFrame || endIsEndOfChildFrame || startIsStartOfFrameAndEndIsEndOfFrameWithCommonParent || isFirstTableCell);
+#endif
+
+ beginEditBlock();
+
+ 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);
+ QTextUndoCommand c = { QTextUndoCommand::Removed, true,
+ op, X->format, X->stringPosition, key, { X->size_array[0] },
+ blockRevision };
+ QTextUndoCommand cInsert = { QTextUndoCommand::Inserted, true,
+ 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 = undoState;
+ x = n;
+
+ if (needsInsert)
+ appendUndoItem(cInsert);
+ }
+ if (w)
+ unite(w);
+
+ Q_ASSERT(blocks.length() == fragments.length());
+
+ endEditBlock();
+}
+
+void QTextDocumentPrivate::remove(int pos, int length, QTextUndoCommand::Operation op)
+{
+ if (length == 0)
+ return;
+ move(pos, -1, length, op);
+}
+
+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;
+ }
+
+ QTextUndoCommand 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<QTextBlockGroup *>(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<QTextBlockGroup *>(objectForFormat(format));
+ if (mode == MergeFormat) {
+ format.merge(newFormat);
+ newFormatIdx = formats.indexForFormat(format);
+ group = qobject_cast<QTextBlockGroup *>(objectForFormat(format));
+ }
+ block(it)->format = newFormatIdx;
+
+ block(it)->invalidate();
+
+ QTextUndoCommand 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();
+ 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;
+ 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;
+ 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;
+ 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;
+ 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;
+ 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<QTextBlockGroup *>(objectForFormat(formats.blockFormat(oldFormat)));
+ QTextBlockGroup *group = qobject_cast<QTextBlockGroup *>(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());
+ 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;
+ break;
+ }
+ case QTextUndoCommand::Custom:
+ resetBlockRevision = -1; // ## TODO
+ if (undo)
+ c.custom->undo();
+ else
+ c.custom->redo();
+ break;
+ default:
+ Q_ASSERT(false);
+ }
+
+ if (resetBlockRevision >= 0) {
+ int b = blocks.findNode(resetBlockRevision);
+ QTextBlockData *B = blocks.fragment(b);
+ B->revision = c.revision;
+ }
+
+ if (undo) {
+ if (undoState == 0 || !undoStack[undoState-1].block)
+ break;
+ } else {
+ ++undoState;
+ if (undoState == undoStack.size() || !undoStack[undoState-1].block)
+ break;
+ }
+ }
+ undoEnabled = true;
+ int editPos = -1;
+ if (docChangeFrom >= 0) {
+ editPos = qMin(docChangeFrom + docChangeLength, length() - 1);
+ }
+ endEditBlock();
+ emitUndoAvailable(isUndoAvailable());
+ emitRedoAvailable(isRedoAvailable());
+ return editPos;
+}
+
+/*!
+ 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 = editBlock != 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())
+ truncateUndoStack();
+
+ if (!undoStack.isEmpty() && modified) {
+ QTextUndoCommand &last = undoStack[undoState - 1];
+ if (last.tryMerge(c))
+ return;
+ }
+ if (modifiedState > undoState)
+ modifiedState = -1;
+ undoStack.append(c);
+ undoState++;
+ emitUndoAvailable(true);
+ emitRedoAvailable(false);
+}
+
+void QTextDocumentPrivate::truncateUndoStack()
+{
+ if (undoState == undoStack.size())
+ return;
+
+ for (int i = undoState; i < undoStack.size(); ++i) {
+ QTextUndoCommand c = undoStack[i];
+ if (c.command & QTextUndoCommand::Removed) {
+ // ########
+// QTextFragment *f = c.fragment_list;
+// while (f) {
+// QTextFragment *n = f->right;
+// delete f;
+// f = n;
+// }
+ } else if (c.command & QTextUndoCommand::Custom) {
+ delete c.custom;
+ }
+ }
+ undoStack.resize(undoState);
+}
+
+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;
+ truncateUndoStack();
+ emitUndoAvailable(false);
+ emitRedoAvailable(false);
+ }
+ modifiedState = modified ? -1 : undoState;
+ undoEnabled = enable;
+ if (!undoEnabled)
+ compressPieceTable();
+}
+
+void QTextDocumentPrivate::joinPreviousEditBlock()
+{
+ beginEditBlock();
+
+ if (undoEnabled && undoState)
+ undoStack[undoState - 1].block = true;
+}
+
+void QTextDocumentPrivate::endEditBlock()
+{
+ Q_Q(QTextDocument);
+ if (--editBlock)
+ return;
+
+ if (undoEnabled && undoState > 0) {
+ const bool wasBlocking = undoStack[undoState - 1].block;
+ undoStack[undoState - 1].block = false;
+ if (wasBlocking)
+ emit document()->undoCommandAdded();
+ }
+
+ 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;
+ }
+ }
+
+ while (!changedCursors.isEmpty()) {
+ QTextCursorPrivate *curs = changedCursors.takeFirst();
+ emit q->cursorPositionChanged(QTextCursor(curs));
+ }
+
+ 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.
+*/
+void QTextDocumentPrivate::adjustDocumentChangesAndCursors(int from, int addedOrRemoved, QTextUndoCommand::Operation op)
+{
+ Q_Q(QTextDocument);
+ for (int i = 0; i < cursors.size(); ++i) {
+ QTextCursorPrivate *curs = cursors.at(i);
+ if (curs->adjustPosition(from, addedOrRemoved, op) == QTextCursorPrivate::CursorMoved) {
+ if (editBlock) {
+ if (!changedCursors.contains(curs))
+ changedCursors.append(curs);
+ } else {
+ emit q->cursorPositionChanged(QTextCursor(curs));
+ }
+ }
+ }
+
+// 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);
+ contentsChanged();
+ 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);
+
+ contentsChanged();
+}
+
+
+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<QTextBlockGroup *>(obj);
+ if (b) {
+ b->d_func()->markBlocksDirty();
+ }
+ QTextFrame *f = qobject_cast<QTextFrame *>(obj);
+ if (f)
+ documentChange(f->firstPosition(), f->lastPosition() - f->firstPosition());
+
+ QTextUndoCommand c = { QTextUndoCommand::GroupFormatChange, true, QTextUndoCommand::MoveCursor, oldFormatIndex,
+ 0, 0, { obj->d_func()->objectIndex }, 0 };
+ appendUndoItem(c);
+
+ endEditBlock();
+}
+
+static QTextFrame *findChildFrame(QTextFrame *f, int pos)
+{
+ // ##### use binary search
+ QList<QTextFrame *> children = f->childFrames();
+ for (int i = 0; i < children.size(); ++i) {
+ QTextFrame *c = children.at(i);
+ if (pos >= c->firstPosition() && pos <= c->lastPosition())
+ return c;
+ }
+ return 0;
+}
+
+QTextFrame *QTextDocumentPrivate::rootFrame() const
+{
+ if (!rtFrame) {
+ QTextFrameFormat defaultRootFrameFormat;
+ defaultRootFrameFormat.setMargin(documentMargin);
+ rtFrame = qobject_cast<QTextFrame *>(const_cast<QTextDocumentPrivate *>(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)
+{
+ // ###### optimise
+ 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<QTextFrame *>(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<QTextFrame *>(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<QTextDocumentPrivate *>(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()->pieceTable = this;
+ 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) {
+ qMemCopy(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);
+ for (int i = 0; i < cursors.size(); ++i)
+ cursors.at(i)->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..25763e16fe
--- /dev/null
+++ b/src/gui/text/qtextdocument_p.h
@@ -0,0 +1,398 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <iostream>
+#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,
+ Custom = 256
+ };
+ enum Operation {
+ KeepCursor = 0,
+ MoveCursor = 1
+ };
+ quint16 command;
+ quint8 block; ///< All undo commands that have this set to zero/false are combined with the preceding command on undo/redo.
+ 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<QTextFragmentData> FragmentMap;
+ typedef FragmentMap::ConstIterator FragmentIterator;
+ typedef QFragmentMap<QTextBlockData> 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() { editBlock++; }
+ void joinPreviousEditBlock();
+ void endEditBlock();
+ 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 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<QTextDocumentPrivate *>(this), blocks.firstNode()); }
+ inline QTextBlock blocksEnd() const { return QTextBlock(const_cast<QTextDocumentPrivate *>(this), 0); }
+ inline QTextBlock blocksFind(int pos) const { return QTextBlock(const_cast<QTextDocumentPrivate *>(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); }
+
+private:
+ bool split(int pos);
+ bool unite(uint f);
+ void truncateUndoStack();
+
+ 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); changedCursors.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<QTextUndoCommand> undoStack;
+ bool undoEnabled;
+ int undoState;
+ // position in undo stack of the last setModified(false) call
+ int modifiedState;
+ bool modified;
+
+ int editBlock;
+ int docChangeFrom;
+ int docChangeOldLength;
+ int docChangeLength;
+ bool framesDirty;
+
+ QTextFormatCollection formats;
+ mutable QTextFrame *rtFrame;
+ QAbstractTextDocumentLayout *lout;
+ FragmentMap fragments;
+ BlockMap blocks;
+ int initialBlockCharFormatIndex;
+
+ QList<QTextCursorPrivate*> cursors;
+ QList<QTextCursorPrivate*> changedCursors;
+ QMap<int, QTextObject *> objects;
+ QMap<QUrl, QVariant> resources;
+ QMap<QUrl, QVariant> cachedResources;
+ QString defaultStyleSheet;
+
+ int lastBlockCount;
+
+public:
+ QTextOption defaultTextOption;
+#ifndef QT_NO_CSSPARSER
+ QCss::StyleSheet parsedDefaultStyleSheet;
+#endif
+ int maximumBlockCount;
+ bool needsEnsureMaximumBlockCount;
+ bool inContentsChange;
+ 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..21958a67de
--- /dev/null
+++ b/src/gui/text/qtextdocumentfragment.cpp
@@ -0,0 +1,1217 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtextdocumentfragment.h"
+#include "qtextdocumentfragment_p.h"
+#include "qtextcursor_p.h"
+#include "qtextlist.h"
+#include "private/qunicodetables_p.h"
+
+#include <qdebug.h>
+#include <qtextcodec.h>
+#include <qbytearray.h>
+#include <qdatastream.h>
+#include <qdatetime.h>
+
+QT_BEGIN_NAMESPACE
+
+QTextCopyHelper::QTextCopyHelper(const QTextCursor &_source, const QTextCursor &_destination, bool forceCharFormat, const QTextCharFormat &fmt)
+ : formatCollection(*_destination.d->priv->formatCollection()), originalText(_source.d->priv->buffer())
+{
+ 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 text
+ \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<QTextDocument *>(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("<!--StartFragment-->"));
+ if (startFragmentPos != -1) {
+ QString qt3RichTextHeader(QLatin1String("<meta name=\"qrichtext\" content=\"1\" />"));
+
+ // Hack for Qt3
+ const bool hasQtRichtextMetaTag = html.contains(qt3RichTextHeader);
+
+ const int endFragmentPos = html.indexOf(QLatin1String("<!--EndFragment-->"));
+ 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
+ * <table>, <ul> or <img> 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 <ul><li>foo</ul>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<QTextHtmlParserNode *>(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);
+
+ ++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: <ul>Text here<li>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<QTextLength> columnWidths;
+
+ int tableHeaderRowCount = 0;
+ QVector<int> 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<RowColSpanInfo> rowColSpans;
+ QVector<RowColSpanInfo> 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, "<b>bold</b>" 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, "<b>bold</b>" 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..269aca2bcb
--- /dev/null
+++ b/src/gui/text/qtextdocumentfragment.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTDOCUMENTFRAGMENT_H
+#define QTEXTDOCUMENTFRAGMENT_H
+
+#include <QtCore/qstring.h>
+
+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..743ed9d2b6
--- /dev/null
+++ b/src/gui/text/qtextdocumentfragment_p.h
@@ -0,0 +1,236 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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<int, int> 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<QTextList> list;
+ };
+ QVector<List> 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<QTextFrame> frame;
+ bool isTextFrame;
+ int rows;
+ int columns;
+ int currentRow; // ... for buggy html (see html_skipCell testcase)
+ TableCellIterator currentCell;
+ int lastIndent;
+ };
+ QVector<Table> 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..c66d0c1ed0
--- /dev/null
+++ b/src/gui/text/qtextdocumentlayout.cpp
@@ -0,0 +1,3224 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <qpainter.h>
+#include <qmath.h>
+#include <qrect.h>
+#include <qpalette.h>
+#include <qdebug.h>
+#include <qvarlengtharray.h>
+#include <limits.h>
+#include <qstyle.h>
+#include <qbasictimer.h>
+#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
+
+extern int qt_defaultDpi();
+
+// ################ should probably add frameFormatChange notification!
+
+struct QLayoutStruct;
+
+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;
+
+ QLayoutStruct *currentLayoutStruct;
+
+ bool sizeDirty;
+ bool layoutDirty;
+
+ QList<QPointer<QTextFrame> > floats;
+};
+
+QTextFrameData::QTextFrameData()
+ : maximumWidth(QFIXED_MAX),
+ currentLayoutStruct(0), sizeDirty(true), layoutDirty(true)
+{
+}
+
+struct QLayoutStruct {
+ QLayoutStruct() : 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<QTextFrame *> 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<QFixed> minWidths;
+ QVector<QFixed> maxWidths;
+ QVector<QFixed> widths;
+ QVector<QFixed> heights;
+ QVector<QFixed> columnPositions;
+ QVector<QFixed> rowPositions;
+
+ QVector<QFixed> cellVerticalOffsets;
+
+ QFixed headerHeight;
+
+ // maps from cell index (row + col * rowCount) to child frames belonging to
+ // the specific cell
+ QMultiHash<int, QTextFrame *> 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.type() == QVariant::Double);
+ return QFixed::fromReal(v.toDouble() * 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<QTextTable *>(f))
+ data = new QTextTableData;
+ else
+ data = new QTextFrameData;
+ f->setLayoutData(data);
+ return data;
+}
+
+static inline QTextFrameData *data(QTextFrame *f)
+{
+ QTextFrameData *data = static_cast<QTextFrameData *>(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<QTextTable *>(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<const QTextTable *>(previousFrame)
+ && block.isValid()
+ && block.length() == 1
+ && previousFrame->lastPosition() == block.position() - 1
+ ;
+}
+
+static inline bool isLineSeparatorBlockAfterTable(const QTextBlock &block, const QTextFrame *previousFrame)
+{
+ return qobject_cast<const QTextTable *>(previousFrame)
+ && block.isValid()
+ && block.length() > 1
+ && block.text().at(0) == QChar::LineSeparator
+ && previousFrame->lastPosition() == block.position() - 1
+ ;
+}
+
+/*
+
+Optimisation 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<QGradient *>(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<QTextFrame *> &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;
+
+ QLayoutStruct 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,
+ QLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, const QTextBlockFormat *previousBlockFormat);
+ void layoutFlow(QTextFrame::Iterator it, QLayoutStruct *layoutStruct, int layoutFrom, int layoutTo, QFixed width = 0);
+ void pageBreakInsideTable(QTextTable *table, QLayoutStruct *layoutStruct);
+
+
+ void floatMargins(const QFixed &y, const QLayoutStruct *layoutStruct, QFixed *left, QFixed *right) const;
+ QFixed findY(QFixed yFrom, const QLayoutStruct *layoutStruct, QFixed requiredWidth) const;
+
+ QVector<QCheckPoint> 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<QCheckPoint>::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<QTextTable *>(frame)) {
+ const int rows = table->rows();
+ const int columns = table->columns();
+ QTextTableData *td = static_cast<QTextTableData *>(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<QTextFrame *> 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<QTextFrame *> 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<QTextTableData *>(data(table));
+
+ QVector<QFixed>::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<QFixed>::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<int>(rect.top() / pageHeight) : 0;
+ const int bottomPage = pageHeight > 0 ? static_cast<int>((rect.bottom() + border) / pageHeight) : 0;
+
+#ifndef QT_NO_CSSPARSER
+ QCss::BorderStyle cssStyle = static_cast<QCss::BorderStyle>(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
+{
+ 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();
+ }
+
+ 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);
+ }
+}
+
+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<QTextTable *>(frame);
+ const QRectF frameRect(off, fd->size.toSizeF());
+
+ if (table) {
+ const int rows = table->rows();
+ const int columns = table->columns();
+ QTextTableData *td = static_cast<QTextTableData *>(data(table));
+
+ QVarLengthArray<int> 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<QFixed>::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<QTextFrame *> 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<QTextFrame *> &floats, QTextBlock *cursorBlockNeedingRepaint) const
+{
+ Q_Q(const QTextDocumentLayout);
+ const bool inRootFrame = (!it.atEnd() && it.parentFrame() && it.parentFrame()->parentFrame() == 0);
+
+ QVector<QCheckPoint>::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<QTextDocumentLayout *>(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<QTextLayout::FormatRange> 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 = docPrivate->defaultTextOption.textDirection();
+ if (blockFormat.hasProperty(QTextFormat::LayoutDirection))
+ dir = blockFormat.layoutDirection();
+ {
+ 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:
+ itemText = static_cast<QTextList *>(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: {
+ QTextLayout layout(itemText, font, q->paintDevice());
+ layout.setCacheEnabled(true);
+ QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute);
+ option.setTextDirection(dir);
+ layout.setTextOption(option);
+ layout.beginLayout();
+ layout.createLine();
+ layout.endLayout();
+ layout.draw(painter, QPointF(r.left(), pos.y()));
+ break;
+ }
+ case QTextListFormat::ListSquare:
+ painter->fillRect(r, brush);
+ break;
+ case QTextListFormat::ListCircle:
+ painter->drawEllipse(r);
+ break;
+ case QTextListFormat::ListDisc:
+ painter->setBrush(brush);
+ painter->setPen(Qt::NoPen);
+ painter->drawEllipse(r);
+ painter->setBrush(Qt::NoBrush);
+ 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());
+}
+
+QLayoutStruct QTextDocumentLayoutPrivate::layoutCell(QTextTable *t, const QTextTableCell &cell, QFixed width,
+ int layoutFrom, int layoutTo, QTextTableData *td,
+ QFixed absoluteTableY, bool withPageBreaks)
+{
+ LDEBUG << "layoutCell";
+ QLayoutStruct 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<QTextFrame *> 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 <td><img align="right" src="..." />blah</td>
+ // 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<QTextTableData *>(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<QTextFrame *> 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<QTextLength> 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
+ QLayoutStruct 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<int> 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<QFixed> cellHeights;
+ cellHeights.reserve(rows * columns);
+
+ QFixed pageHeight = QFixed::fromReal(document->pageSize().height());
+ if (pageHeight <= 0)
+ pageHeight = QFIXED_MAX;
+
+ QVector<QFixed> 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;
+ QLayoutStruct 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);
+
+ QLayoutStruct *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;
+ }
+ }
+
+ if (y + layoutStruct->frameY + fd->size.height > layoutStruct->pageBottom) {
+ layoutStruct->newPage();
+ y = layoutStruct->y;
+ }
+
+ 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;
+}
+
+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<QTextTable *>(parent)) {
+ const QTextTableData *td = static_cast<const QTextTableData *>(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<QTextTable *>(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;
+
+ QLayoutStruct 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<QTextFrame *> 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, QLayoutStruct *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<QCheckPoint>::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<QTextTable *>(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<QTextTableData *>(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);
+
+ 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<QTextTableData *>(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<QTextTableData *>(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<QTextTable *>(layoutStruct->frame)) {
+ QList<QTextFrame *> 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;
+}
+
+void QTextDocumentLayoutPrivate::layoutBlock(const QTextBlock &bl, int blockPosition, const QTextBlockFormat &blockFormat,
+ QLayoutStruct *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 = docPrivate->defaultTextOption.textDirection();
+ if (blockFormat.hasProperty(QTextFormat::LayoutDirection))
+ dir = blockFormat.layoutDirection();
+
+ 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;
+
+ 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=" <<right;
+
+ if (fixedColumnWidth != -1)
+ line.setNumColumns(fixedColumnWidth, (right - left).toReal());
+ else
+ line.setLineWidth((right - left).toReal());
+
+// qDebug() << "layoutBlock; layouting line with width" << right - left << "->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();
+
+ if (haveWordOrAnyWrapMode) {
+ option.setWrapMode(QTextOption::WrapAnywhere);
+ tl->setTextOption(option);
+ }
+
+ line.setLineWidth((right-left).toReal());
+ if (QFixed::fromReal(line.naturalTextWidth()) > right-left) {
+ 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<qreal>(line.naturalTextWidth(), (right-left).toReal()));
+ }
+
+ if (haveWordOrAnyWrapMode) {
+ option.setWrapMode(QTextOption::WordWrap);
+ tl->setTextOption(option);
+ }
+ }
+
+ QFixed lineHeight = QFixed::fromReal(line.height());
+ if (layoutStruct->pageHeight > 0 && layoutStruct->absoluteY() + lineHeight > 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).toReal()));
+ layoutStruct->y += lineHeight;
+ layoutStruct->contentsWidth
+ = qMax<QFixed>(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);
+ const QFixed lineHeight = QFixed::fromReal(line.height());
+ if (layoutStruct->pageHeight != QFIXED_MAX) {
+ if (layoutStruct->absoluteY() + lineHeight > layoutStruct->pageBottom)
+ layoutStruct->newPage();
+ line.setPosition(QPointF(line.position().x(), layoutStruct->y.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 QLayoutStruct *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="<<y;
+}
+
+QFixed QTextDocumentLayoutPrivate::findY(QFixed yFrom, const QLayoutStruct *layoutStruct, QFixed requiredWidth) const
+{
+ QFixed right, left;
+ requiredWidth = qMin(requiredWidth, layoutStruct->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<QTextFrame *> 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<QTextFrame *>(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<QTextFrame *>(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<QTextFrame *>(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<QTextDocumentLayout *>(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<QTextTable *>(f)) {
+ QTextTableCell cell = table->cellAt(framePos);
+ if (cell.isValid())
+ pos += static_cast<QTextTableData *>(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())
+ 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<QTextTable *>(frame)) {
+ QTextTableCell cell = table->cellAt(blockPos);
+ if (cell.isValid())
+ offset += static_cast<QTextTableData *>(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
+{
+ QPaintDevice *dev = q_func()->paintDevice();
+ if (!dev)
+ return value;
+ return value * dev->logicalDpiY() / qreal(qt_defaultDpi());
+}
+
+QFixed QTextDocumentLayoutPrivate::scaleToDevice(QFixed value) const
+{
+ QPaintDevice *dev = q_func()->paintDevice();
+ if (!dev)
+ return value;
+ return value * QFixed(dev->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..d0206ab899
--- /dev/null
+++ b/src/gui/text/qtextdocumentlayout_p.h
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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..08ee14e914
--- /dev/null
+++ b/src/gui/text/qtextdocumentwriter.cpp
@@ -0,0 +1,372 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "qtextdocumentwriter.h"
+
+#include <QtCore/qfile.h>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qtextcodec.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qdebug.h>
+#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 text
+ \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<QFile *>(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<QFile *>(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<QByteArray> QTextDocumentWriter::supportedDocumentFormats()
+{
+ QList<QByteArray> 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..53d17ca2db
--- /dev/null
+++ b/src/gui/text/qtextdocumentwriter.h
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef QTEXTDOCUMENTWRITER_H
+#define QTEXTDOCUMENTWRITER_H
+
+#include <QtCore/qstring.h>
+
+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<QByteArray> 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..80a5425790
--- /dev/null
+++ b/src/gui/text/qtextengine.cpp
@@ -0,0 +1,2648 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <private/qunicodetables_p.h>
+#include "qtextdocument_p.h"
+#include <qapplication.h>
+#include <stdlib.h>
+
+
+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(m_string.utf16(), 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 <iostream>
+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::DirON; 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 <private/qharfbuzz_p.h>
+
+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<const QChar *>(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;
+ }
+}
+
+extern int qt_defaultDpiY(); // in qfont.cpp
+
+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_OS_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 *= 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 {
+ const QFixed advance = glyphs.advances_x[i-1];
+ glyphs.advances_x[i-1] += (letterSpacing - 100) * advance / 100;
+ }
+ }
+ }
+ if (letterSpacingIsAbsolute)
+ glyphs.advances_x[si.num_glyphs-1] += letterSpacing;
+ else {
+ const QFixed advance = glyphs.advances_x[si.num_glyphs-1];
+ glyphs.advances_x[si.num_glyphs-1] += (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];
+}
+
+#if defined(Q_OS_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);
+
+ 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<const QChar *>(uc);
+ }
+
+ while (true) {
+ ensureSpace(num_glyphs);
+ 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<const ushort *>(str);
+ if ((si.analysis.flags == QScriptAnalysis::SmallCaps || si.analysis.flags == QScriptAnalysis::Uppercase
+ || si.analysis.flags == QScriptAnalysis::Lowercase)
+ && uc != upperCased)
+ delete [] uc;
+}
+#endif
+
+/// 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);
+
+ bool kerningEnabled = this->font(si).d->kerning;
+
+ HB_ShaperItem entire_shaper_item;
+ entire_shaper_item.kerning_applied = false;
+ entire_shaper_item.string = reinterpret_cast<const HB_UChar16 *>(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;
+ entire_shaper_item.glyphIndicesPresent = false;
+
+ HB_UChar16 upperCased[256]; // XXX what about making this 4096, so we don't have to extend it ever.
+ if (si.analysis.flags == QScriptAnalysis::SmallCaps || si.analysis.flags == QScriptAnalysis::Uppercase
+ || si.analysis.flags == QScriptAnalysis::Lowercase) {
+ 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));
+ ensureSpace(entire_shaper_item.num_glyphs);
+ QGlyphLayout initialGlyphs = availableGlyphs(&si).mid(0, entire_shaper_item.num_glyphs);
+
+ if (!stringToGlyphs(&entire_shaper_item, &initialGlyphs, font)) {
+ ensureSpace(entire_shaper_item.num_glyphs);
+ 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 ((si.analysis.flags == QScriptAnalysis::SmallCaps || si.analysis.flags == QScriptAnalysis::Uppercase
+ || si.analysis.flags == QScriptAnalysis::Lowercase) && entire_shaper_item.string != upperCased)
+ delete [] const_cast<HB_UChar16 *>(entire_shaper_item.string);
+ return;
+ }
+ }
+
+ // split up the item into parts that come from different font engines.
+ QVarLengthArray<int> 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 initial_glyph_pos = 0;
+ 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;
+
+ QFontEngine *actualFontEngine = font;
+ uint engineIdx = 0;
+ if (font->type() == QFontEngine::Multi) {
+ engineIdx = uint(initialGlyphs.glyphs[itemBoundaries[k + 1]] >> 24);
+
+ actualFontEngine = static_cast<QFontEngineMulti *>(font)->engine(engineIdx);
+ }
+
+ shaper_item.font = actualFontEngine->harfbuzzFont();
+ shaper_item.face = actualFontEngine->harfbuzzFace();
+
+ shaper_item.glyphIndicesPresent = true;
+
+ do {
+ ensureSpace(glyph_pos + shaper_item.num_glyphs);
+ initialGlyphs = availableGlyphs(&si).mid(0, entire_shaper_item.num_glyphs);
+ shaper_item.num_glyphs = layoutData->glyphLayout.numGlyphs - layoutData->used - glyph_pos;
+
+ const QGlyphLayout g = availableGlyphs(&si);
+ shaper_item.glyphs = g.glyphs + glyph_pos;
+ shaper_item.attributes = g.attributes + glyph_pos;
+ shaper_item.advances = reinterpret_cast<HB_Fixed *>(g.advances_x + glyph_pos);
+ shaper_item.offsets = reinterpret_cast<HB_FixedPoint *>(g.offsets + glyph_pos);
+
+ 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);
+
+ for (hb_uint32 i = 0; i < shaper_item.item.length; ++i) {
+ g.glyphs[i] = g.glyphs[i] | (engineIdx << 24);
+ 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;
+
+ initial_glyph_pos += shaper_item.initialGlyphCount;
+ }
+
+// 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 ((si.analysis.flags == QScriptAnalysis::SmallCaps || si.analysis.flags == QScriptAnalysis::Uppercase)
+ && entire_shaper_item.string != upperCased)
+ delete [] const_cast<HB_UChar16 *>(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)
+ : fnt(f)
+{
+ init(this);
+ text = str;
+}
+
+QTextEngine::~QTextEngine()
+{
+ if (!stackEngine)
+ delete layoutData;
+ delete specialData;
+}
+
+const HB_CharAttributes *QTextEngine::attributes() const
+{
+ if (layoutData && layoutData->haveCharAttributes)
+ return (HB_CharAttributes *) layoutData->memory;
+
+ itemize();
+ ensureSpace(layoutData->string.length());
+
+ QVarLengthArray<HB_ScriptItem> 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<const HB_UChar16 *>(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<QTextEngine *>(this)),
+ layoutData->items[item].position + block.position(), format);
+ }
+ } else if (layoutData->items[item].analysis.flags == QScriptAnalysis::Tab) {
+ // set up at least the ascent/descent of the script item for the tab
+ fontEngine(layoutData->items[item], &layoutData->items[item].ascent, &layoutData->items[item].descent);
+ } else {
+ shapeText(item);
+ }
+}
+
+void QTextEngine::invalidate()
+{
+ freeMemory();
+ minWidth = 0;
+ maxWidth = 0;
+ if (specialData)
+ specialData->resolvedFormatIndices.clear();
+}
+
+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;
+
+ bool ignore = ignoreBidi;
+ if (!ignore && option.textDirection() == Qt::LeftToRight) {
+ 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<QScriptAnalysis, 4096> scriptAnalysis(length);
+ QScriptAnalysis *analysis = scriptAnalysis.data();
+
+ QBidiControl control(option.textDirection() == Qt::RightToLeft);
+
+ 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<QTextEngine *>(this), analysis, control);
+ }
+
+ const ushort *unicode = layoutData->string.utf16();
+ // correctly assign script, isTab and isObject to the script analysis
+ const ushort *uc = 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<ushort*>(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<QFont::Capitalization> (fnt.d->capital));
+ }
+
+ addRequiredBoundaries();
+ resolveAdditionalFormats();
+}
+
+int QTextEngine::findItem(int strPos) const
+{
+ itemize();
+
+ // ##### use binary search
+ int item;
+ for (item = layoutData->items.size()-1; item > 0; --item) {
+ if (layoutData->items[item].position <= strPos)
+ break;
+ }
+ return item;
+}
+
+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 + len > 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) {
+ 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->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;
+}
+
+QFontEngine *QTextEngine::fontEngine(const QScriptItem &si, QFixed *ascent, QFixed *descent) const
+{
+ QFontEngine *engine = 0;
+ QFontEngine *scaledEngine = 0;
+ int script = si.analysis.script;
+
+ 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);
+ }
+ 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);
+ }
+ } else {
+ engine = font.d->engineForScript(script);
+ }
+
+ if (si.analysis.flags == QScriptAnalysis::SmallCaps) {
+ QFontPrivate *p = font.d->smallCapsFontPrivate();
+ scaledEngine = p->engineForScript(script);
+ }
+
+ if (ascent) {
+ *ascent = engine->ascent();
+ *descent = engine->descent();
+ }
+
+ 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<QScriptLine &>(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 explicitely 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()+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<QJustificationPoint> 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));
+ 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));
+ 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<QScriptLine &>(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<QScriptLine &>(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);
+ }
+
+ ascent = qMax(ascent, e->ascent());
+ descent = qMax(descent, e->descent());
+}
+
+QTextEngine::LayoutData::LayoutData()
+{
+ memory = 0;
+ allocated = 0;
+ memory_on_stack = false;
+ used = 0;
+ hasBidi = false;
+ inLayout = false;
+ 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<char *>(m), str.length());
+ glyphLayout.clear();
+ memset(memory, 0, space_charAttributes*sizeof(void *));
+ }
+ used = 0;
+ hasBidi = false;
+ inLayout = false;
+ haveCharAttributes = false;
+}
+
+QTextEngine::LayoutData::~LayoutData()
+{
+ if (!memory_on_stack)
+ free(memory);
+ memory = 0;
+}
+
+void QTextEngine::LayoutData::reallocate(int totalGlyphs)
+{
+ Q_ASSERT(totalGlyphs >= glyphLayout.numGlyphs);
+ if (memory_on_stack && available_glyphs >= totalGlyphs) {
+ glyphLayout.grow(glyphLayout.data(), totalGlyphs);
+ return;
+ }
+
+ 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;
+ Q_ASSERT(newAllocated >= allocated);
+ void **old_mem = memory;
+ memory = (void **)::realloc(memory_on_stack ? 0 : old_mem, newAllocated*sizeof(void *));
+ if (memory_on_stack && memory)
+ memcpy(memory, old_mem, allocated*sizeof(void *));
+ 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<char *>(m), totalGlyphs);
+
+ allocated = newAllocated;
+}
+
+// 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->inLayout = false;
+ 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 '~':
+ 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();
+ }
+}
+
+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();
+ for (int i = 0; i < layoutData->items.size(); ++i) {
+ QScriptItem &si = layoutData->items[i];
+ if (!si.num_glyphs)
+ shape(i);
+
+ HB_CharAttributes *attributes = const_cast<HB_CharAttributes *>(this->attributes());
+ 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 (i < end - 1
+ && 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<QFontEngineMulti *>(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 (mode == Qt::ElideRight) {
+ QFixed currentWidth;
+ int pos = 0;
+ 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);
+
+ return layoutData->string.left(pos) + ellipsisText;
+ } else if (mode == Qt::ElideLeft) {
+ QFixed currentWidth;
+ int pos = layoutData->string.length();
+ 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);
+
+ 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);
+
+ 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[itemToSplit].position <= strPos)
+ itemToSplit++;
+ itemToSplit--;
+ if (layoutData->items[itemToSplit].position == strPos) {
+ // already a split at the requested position
+ return;
+ }
+ splitItem(itemToSplit, strPos - layoutData->items[itemToSplit].position);
+}
+
+void QTextEngine::splitItem(int item, int pos) const
+{
+ if (pos <= 0)
+ return;
+
+ layoutData->items.insert(item + 1, QScriptItem(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);
+}
+
+extern int qt_defaultDpiY();
+
+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<QTextOption::Tab> tabArray = option.tabs();
+ if (!tabArray.isEmpty()) {
+ if (option.textDirection() == Qt::RightToLeft) { // rebase the tabArray positions.
+ QList<QTextOption::Tab> newTabs;
+ QList<QTextOption::Tab>::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<int> 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;
+}
+
+QStackTextEngine::QStackTextEngine(const QString &string, const QFont &f)
+ : _layoutData(string, _memory, MemSize)
+{
+ fnt = f;
+ text = string;
+ 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::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..d122317f7d
--- /dev/null
+++ b/src/gui/text/qtextengine_mac.cpp
@@ -0,0 +1,656 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtextengine_p.h"
+
+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_ASSERT(num_glyphs <= length);
+ 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);
+ if (font->type() != QFontEngine::Multi) {
+ shapeTextWithHarfbuzz(item);
+ return;
+ }
+
+#ifndef QT_MAC_USE_COCOA
+ QFontEngineMacMulti *fe = static_cast<QFontEngineMacMulti *>(font);
+#else
+ QCoreTextFontEngineMulti *fe = static_cast<QCoreTextFontEngineMulti *>(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<const QChar *>(uc);
+ }
+
+ while (true) {
+ ensureSpace(num_glyphs);
+ num_glyphs = layoutData->glyphLayout.numGlyphs - layoutData->used;
+
+ QGlyphLayout g = availableGlyphs(&si);
+ g.numGlyphs = num_glyphs;
+ unsigned short *log_clusters = logClusters(&si);
+
+ if (fe->stringToCMap(str,
+ len,
+ &g,
+ &num_glyphs,
+ flags,
+ log_clusters,
+ attributes())) {
+
+ heuristicSetGlyphAttributes(str, len, &g, log_clusters, num_glyphs);
+ break;
+ }
+ }
+
+ si.num_glyphs = num_glyphs;
+
+ layoutData->used += si.num_glyphs;
+
+ QGlyphLayout g = shapedGlyphs(&si);
+
+ if (si.analysis.script == QUnicodeTables::Arabic) {
+ QVarLengthArray<QArabicProperties> 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<const ushort *>(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..cf241faf14
--- /dev/null
+++ b/src/gui/text/qtextengine_p.h
@@ -0,0 +1,608 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 <stdlib.h>
+
+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;
+};
+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 : 8;
+ 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<QFixedPoint *>(address);
+ int offset = totalGlyphs * sizeof(HB_FixedPoint);
+ glyphs = reinterpret_cast<HB_Glyph *>(address + offset);
+ offset += totalGlyphs * sizeof(HB_Glyph);
+ advances_x = reinterpret_cast<QFixed *>(address + offset);
+ offset += totalGlyphs * sizeof(QFixed);
+ advances_y = reinterpret_cast<QFixed *>(address + offset);
+ offset += totalGlyphs * sizeof(QFixed);
+ justifications = reinterpret_cast<QGlyphJustification *>(address + offset);
+ offset += totalGlyphs * sizeof(QGlyphJustification);
+ attributes = reinterpret_cast<HB_GlyphAttributes *>(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<char *>(offsets + numGlyphs) == reinterpret_cast<char *>(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<char *>(offsets);
+ }
+
+ void grow(char *address, int totalGlyphs);
+};
+
+class QVarLengthGlyphLayoutArray : private QVarLengthArray<void *>, public QGlyphLayout
+{
+private:
+ typedef QVarLengthArray<void *> Array;
+public:
+ QVarLengthGlyphLayoutArray(int totalGlyphs)
+ : Array(spaceNeededForGlyphLayout(totalGlyphs) / sizeof(void *) + 1)
+ , QGlyphLayout(reinterpret_cast<char *>(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<char *>(Array::data()), totalGlyphs);
+ memset(Array::data(), 0, Array::size() * sizeof(void *));
+ }
+};
+
+template <int N> struct QGlyphLayoutArray : public QGlyphLayout
+{
+public:
+ QGlyphLayoutArray()
+ : QGlyphLayout(reinterpret_cast<char *>(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());
+
+ /// 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), width(-1),
+ glyph_data_offset(0) {}
+ inline QScriptItem(int p, const QScriptAnalysis &a)
+ : position(p), analysis(a),
+ num_glyphs(0), descent(-1), ascent(-1), width(-1),
+ glyph_data_offset(0) {}
+
+ int position;
+ QScriptAnalysis analysis;
+ unsigned short num_glyphs;
+ QFixed descent;
+ QFixed ascent;
+ QFixed width;
+ int glyph_data_offset;
+ QFixed height() const { return ascent + descent + 1; }
+};
+
+
+Q_DECLARE_TYPEINFO(QScriptItem, Q_MOVABLE_TYPE);
+
+typedef QVector<QScriptItem> QScriptItemArray;
+
+struct Q_AUTOTEST_EXPORT QScriptLine
+{
+ // created and filled in QTextLine::layout_helper
+ QScriptLine()
+ : from(0), length(0),
+ justified(0), gridfitted(0),
+ hasTrailingSpaces(0) {}
+ QFixed descent;
+ QFixed ascent;
+ QFixed x;
+ QFixed y;
+ QFixed width;
+ QFixed textWidth;
+ int from;
+ signed int length : 29;
+ mutable uint justified : 1;
+ mutable uint gridfitted : 1;
+ uint hasTrailingSpaces : 1;
+ QFixed height() const { return ascent + descent + 1; }
+ void setDefaultHeight(QTextEngine *eng);
+ void operator+=(const QScriptLine &other);
+};
+Q_DECLARE_TYPEINFO(QScriptLine, Q_PRIMITIVE_TYPE);
+
+
+inline void QScriptLine::operator+=(const QScriptLine &other)
+{
+ descent = qMax(descent, other.descent);
+ ascent = qMax(ascent, other.ascent);
+ textWidth += other.textWidth;
+ length += other.length;
+}
+
+typedef QVector<QScriptLine> QScriptLineArray;
+
+class QFontPrivate;
+class QTextFormatCollection;
+
+class Q_GUI_EXPORT QTextEngine {
+public:
+ 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 inLayout : 1;
+ uint memory_on_stack : 1;
+ bool haveCharAttributes;
+ QString string;
+ void 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;
+
+ 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) 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 void ensureSpace(int nGlyphs) const {
+ if (layoutData->glyphLayout.numGlyphs - layoutData->used < nGlyphs)
+ layoutData->reallocate((((layoutData->used + nGlyphs)*3/2 + 15) >> 4) << 4);
+ }
+
+ 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;
+
+ 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<QTextLayout::FormatRange> addFormats;
+ QVector<int> addFormatIndices;
+ QVector<int> 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);
+
+private:
+ void setBoundary(int strPos) const;
+ void addRequiredBoundaries() const;
+ void shapeText(int item) const;
+ void shapeTextWithHarfbuzz(int item) const;
+#if defined(Q_OS_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..21bfc4daae
--- /dev/null
+++ b/src/gui/text/qtextformat.cpp
@@ -0,0 +1,3063 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtextformat.h"
+#include "qtextformat_p.h"
+
+#include <qvariant.h>
+#include <qdatastream.h>
+#include <qdebug.h>
+#include <qmap.h>
+#include <qhash.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QTextLength
+ \reentrant
+
+ \brief The QTextLength class encapsulates the different types of length
+ used in a QTextDocument.
+
+ \ingroup text
+
+ 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 length.
+
+ \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
+
+ \value VariableLength
+ \value FixedLength
+ \value PercentageLength
+*/
+
+/*!
+ Returns the text length as a QVariant
+*/
+QTextLength::operator QVariant() const
+{
+ return QVariant(QVariant::TextLength, this);
+}
+
+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;
+}
+
+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<Property> 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 &);
+};
+
+static uint variantHash(const QVariant &variant)
+{
+ switch (variant.type()) {
+ case QVariant::Invalid: return 0;
+ case QVariant::Bool: return variant.toBool();
+ case QVariant::Int: return variant.toInt();
+ case QVariant::Double: return static_cast<int>(variant.toDouble());
+ case QVariant::String: return qHash(variant.toString());
+ case QVariant::Color: return qHash(qvariant_cast<QColor>(variant).rgb());
+ default: break;
+ }
+ return qHash(variant.typeName());
+}
+
+uint QTextFormatPrivate::recalcHash() const
+{
+ hashValue = 0;
+ for (QVector<Property>::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.toDouble());
+ 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<QTextCharFormat::UnderlineStyle>(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.toDouble());
+ break;
+ case QTextFormat::FontWordSpacing:
+ f.setWordSpacing(props.at(i).value.toDouble());
+ break;
+ case QTextFormat::FontCapitalization:
+ f.setCapitalization(static_cast<QFont::Capitalization> (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<QFont::StyleHint>(props.at(i).value.toInt()), f.styleStrategy());
+ break;
+ case QTextFormat::FontStyleStrategy:
+ f.setStyleStrategy(static_cast<QFont::StyleStrategy>(props.at(i).value.toInt()));
+ break;
+ case QTextFormat::FontKerning:
+ f.setKerning(props.at(i).value.toBool());
+ break;
+ default:
+ break;
+ }
+ }
+ fnt = f;
+ fontDirty = false;
+}
+
+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<qint32, QVariant> 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<qint32, QVariant>::ConstIterator it = properties.constBegin();
+ it != properties.constEnd(); ++it)
+ fmt.d->insertProperty(it.key(), it.value());
+
+ return stream;
+}
+
+/*!
+ \class QTextFormat
+ \reentrant
+
+ \brief The QTextFormat class provides formatting information for a
+ QTextDocument.
+
+ \ingroup text
+ \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 thing 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 {Text Processing Classes}
+*/
+
+/*!
+ \enum QTextFormat::FormatType
+
+ \value InvalidFormat
+ \value BlockFormat
+ \value CharFormat
+ \value ListFormat
+ \value TableFormat
+ \value FrameFormat
+
+ \value UserFormat
+*/
+
+/*!
+ \enum QTextFormat::Property
+
+ \value ObjectIndex
+
+ Paragraph and character properties
+
+ \value CssFloat
+ \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<QVariant>).
+ \value BlockIndent
+ \value BlockNonBreakableLines
+ \value BlockTrailingHorizontalRulerWidth
+
+ Character properties
+
+ \value FontFamily
+ \value FontPointSize
+ \value FontSizeAdjustment Specifies the change in size given to the fontsize already set using
+ FontPointSize or FontPixelSize.
+ \omitvalue FontSizeIncrement
+ \value FontWeight
+ \value FontItalic
+ \value FontUnderline \e{This property has been deprecated.} Use QTextFormat::TextUnderlineStyle instead.
+ \value FontOverline
+ \value FontStrikeOut
+ \value FontFixedPitch
+ \value FontPixelSize
+ \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
+ \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
+
+ \value UserProperty
+*/
+
+/*!
+ \enum QTextFormat::ObjectTypes
+
+ \value NoObject
+ \value ImageObject
+ \value TableObject
+ \value TableCellObject
+ \value UserObject The first object that can be used for application-specific purposes.
+*/
+
+/*!
+ \enum QTextFormat::PageBreakFlag
+ \since 4.2
+
+ \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
+*/
+
+/*!
+ \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<QTextFormatPrivate::Property> &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.type() != 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
+{
+ if (!d)
+ return 0;
+ const QVariant prop = d->property(propertyId);
+ if (prop.type() != QVariant::Int)
+ return 0;
+ return prop.toInt();
+}
+
+/*!
+ Returns the value of the property specified by \a propertyId. If the
+ property isn't of QVariant::Double 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.type() != QVariant::Double)
+ return 0.;
+ return prop.toDouble(); // ####
+}
+
+/*!
+ 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.type() != 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.type() != QVariant::Color)
+ return QColor();
+ return qvariant_cast<QColor>(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.type() != QVariant::Pen)
+ return QPen(Qt::NoPen);
+ return qvariant_cast<QPen>(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.type() != QVariant::Brush)
+ return QBrush(Qt::NoBrush);
+ return qvariant_cast<QBrush>(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<QTextLength>(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<QTextLength> QTextFormat::lengthVectorProperty(int propertyId) const
+{
+ QVector<QTextLength> vector;
+ if (!d)
+ return vector;
+ const QVariant prop = d->property(propertyId);
+ if (prop.type() != QVariant::List)
+ return vector;
+
+ QList<QVariant> propertyList = prop.toList();
+ for (int i=0; i<propertyList.size(); ++i) {
+ QVariant var = propertyList.at(i);
+ if (var.type() == QVariant::TextLength)
+ vector.append(qvariant_cast<QTextLength>(var));
+ }
+
+ return vector;
+}
+
+/*!
+ Returns the property specified by the given \a propertyId.
+*/
+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.
+*/
+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<QTextLength> &value)
+{
+ if (!d)
+ d = new QTextFormatPrivate;
+ QVariantList list;
+ for (int i=0; i<value.size(); ++i)
+ list << value.at(i);
+ d->insertProperty(propertyId, list);
+}
+
+/*!
+ Clears the value of the property given by \a propertyId
+ */
+void QTextFormat::clearProperty(int propertyId)
+{
+ if (!d)
+ return;
+ d->clearProperty(propertyId);
+}
+
+
+/*!
+ \fn void QTextFormat::setObjectType(int type)
+
+ Sets the text format's object \a type. See \c{ObjectTypes}.
+*/
+
+
+/*!
+ \fn int QTextFormat::objectType() const
+
+ Returns the text format's object type. See \c{ObjectTypes}.
+*/
+
+
+/*!
+ 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.type() != 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<int, QVariant> QTextFormat::properties() const
+{
+ QMap<int, QVariant> 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 text
+
+ 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 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()
+*/
+
+
+/*!
+ \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://qtsoftware.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.type() == QVariant::StringList)
+ return prop.toStringList().value(0);
+ else if (prop.type() != 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.type() == QVariant::StringList)
+ return prop.toStringList();
+ else if (prop.type() != 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 text
+
+ 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 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
+*/
+
+/*!
+ \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<QTextOption::Tab> &tabs)
+{
+ QList<QVariant> list;
+ QList<QTextOption::Tab>::ConstIterator iter = tabs.constBegin();
+ while (iter != tabs.constEnd()) {
+ QVariant v;
+ qVariantSetValue<QTextOption::Tab>(v, *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<QTextOption::Tab> QTextBlockFormat::tabPositions() const
+{
+ QVariant variant = property(TabPositions);
+ if(variant.isNull())
+ return QList<QTextOption::Tab>();
+ QList<QTextOption::Tab> answer;
+ QList<QVariant> variantsList = qvariant_cast<QList<QVariant> >(variant);
+ QList<QVariant>::Iterator iter = variantsList.begin();
+ while(iter != variantsList.end()) {
+ answer.append( qVariantValue<QTextOption::Tab>(*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::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 text
+
+ 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
+ \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. See \c{Style} for the available styles.
+
+ \sa style()
+*/
+
+/*!
+ \fn Style QTextListFormat::style() const
+
+ Returns the list format's style. See \c{Style}.
+
+ \sa setStyle()
+*/
+
+
+/*!
+ \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()
+*/
+
+
+/*!
+ \class QTextFrameFormat
+ \reentrant
+
+ \brief The QTextFrameFormat class provides formatting information for
+ frames in a QTextDocument.
+
+ \ingroup text
+
+ 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
+
+ \value InFlow
+ \value FloatLeft
+ \value FloatRight
+
+*/
+
+/*!
+ \enum QTextFrameFormat::BorderStyle
+ \since 4.3
+
+ \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
+
+*/
+
+/*!
+ \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 text
+
+ 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<QTextLength> &constraints)
+
+ Sets the column width \a constraints for the table.
+
+ \sa columnWidthConstraints() clearColumnWidthConstraints()
+*/
+
+/*!
+ \fn QVector<QTextLength> 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 text
+
+ 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 text
+
+ 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 = format.d ? format.d->hash() : 0;
+ if (hashes.contains(hash)) {
+ for (int i = 0; i < formats.size(); ++i) {
+ if (formats.at(i) == format)
+ return i;
+ }
+ }
+ int idx = formats.size();
+ formats.append(format);
+
+ QTextFormat &f = formats.last();
+ if (!f.d)
+ f.d = new QTextFormatPrivate;
+ f.d->resolveFont(defaultFnt);
+
+ hashes.insert(hash);
+ return idx;
+}
+
+bool QTextFormatCollection::hasFormatCached(const QTextFormat &format) const
+{
+ uint hash = format.d ? format.d->hash() : 0;
+ if (hashes.contains(hash)) {
+ for (int i = 0; i < formats.size(); ++i)
+ if (formats.at(i) == format)
+ return true;
+ }
+ 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..0571d75ef2
--- /dev/null
+++ b/src/gui/text/qtextformat.h
@@ -0,0 +1,902 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTFORMAT_H
+#define QTEXTFORMAT_H
+
+#include <QtGui/qcolor.h>
+#include <QtGui/qfont.h>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qvector.h>
+#include <QtCore/qvariant.h>
+#include <QtGui/qpen.h>
+#include <QtGui/qbrush.h>
+#include <QtGui/qtextoption.h>
+
+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;
+
+Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QTextLength &);
+Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QTextLength &);
+
+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) {}
+
+Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QTextFormat &);
+Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QTextFormat &);
+
+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,
+ BlockNonBreakableLines = 0x1050,
+ BlockTrailingHorizontalRulerWidth = 0x1060,
+
+ // character properties
+ FirstFontProperty = 0x1FE0,
+ FontCapitalization = FirstFontProperty,
+ FontLetterSpacing = 0x1FE1,
+ FontWordSpacing = 0x1FE2,
+ FontStyleHint = 0x1FE3,
+ FontStyleStrategy = 0x1FE4,
+ FontKerning = 0x1FE5,
+ 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,
+
+ // 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,
+
+ // 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<QTextLength> lengthVectorProperty(int propertyId) const;
+
+ void setProperty(int propertyId, const QVector<QTextLength> &lengths);
+
+ QMap<int, QVariant> 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<QTextFormatPrivate> 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<QFont::Capitalization>(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<QFont::StyleHint>(intProperty(FontStyleHint)); }
+ QFont::StyleStrategy fontStyleStrategy() const
+ { return static_cast<QFont::StyleStrategy>(intProperty(FontStyleStrategy)); }
+
+ 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<UnderlineStyle>(intProperty(TextUnderlineStyle)); }
+
+ inline void setVerticalAlignment(VerticalAlignment alignment)
+ { setProperty(TextVerticalAlignment, alignment); }
+ inline VerticalAlignment verticalAlignment() const
+ { return static_cast<VerticalAlignment>(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:
+ 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 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<QTextOption::Tab> &tabs);
+ QList<QTextOption::Tab> 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); }
+
+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,
+ ListStyleUndefined = 0
+ };
+
+ inline void setStyle(Style style);
+ inline Style style() const
+ { return static_cast<Style>(intProperty(ListStyle)); }
+
+ inline void setIndent(int indent);
+ inline int indent() const
+ { return intProperty(ListIndent); }
+
+protected:
+ explicit QTextListFormat(const QTextFormat &fmt);
+ friend class QTextFormat;
+};
+
+inline void QTextListFormat::setStyle(Style astyle)
+{ setProperty(ListStyle, astyle); }
+
+inline void QTextListFormat::setIndent(int aindent)
+{ setProperty(ListIndent, aindent); }
+
+class Q_GUI_EXPORT QTextImageFormat : public QTextCharFormat
+{
+public:
+ QTextImageFormat();
+
+ bool isValid() const { return isImageFormat(); }
+
+ inline void setName(const QString &name);
+ inline QString name() const
+ { return stringProperty(ImageName); }
+
+ inline void setWidth(qreal width);
+ inline qreal width() const
+ { return doubleProperty(ImageWidth); }
+
+ inline void setHeight(qreal height);
+ inline qreal height() const
+ { return doubleProperty(ImageHeight); }
+
+protected:
+ explicit QTextImageFormat(const QTextFormat &format);
+ friend class QTextFormat;
+};
+
+inline void QTextImageFormat::setName(const QString &aname)
+{ setProperty(ImageName, aname); }
+
+inline void QTextImageFormat::setWidth(qreal awidth)
+{ setProperty(ImageWidth, awidth); }
+
+inline void QTextImageFormat::setHeight(qreal aheight)
+{ setProperty(ImageHeight, aheight); }
+
+class Q_GUI_EXPORT QTextFrameFormat : public QTextFormat
+{
+public:
+ QTextFrameFormat();
+
+ bool isValid() const { return isFrameFormat(); }
+
+ enum Position {
+ InFlow,
+ FloatLeft,
+ FloatRight
+ // ######
+// Absolute
+ };
+
+ enum BorderStyle {
+ BorderStyle_None,
+ BorderStyle_Dotted,
+ BorderStyle_Dashed,
+ BorderStyle_Solid,
+ BorderStyle_Double,
+ BorderStyle_DotDash,
+ BorderStyle_DotDotDash,
+ BorderStyle_Groove,
+ BorderStyle_Ridge,
+ BorderStyle_Inset,
+ BorderStyle_Outset
+ };
+
+ inline void setPosition(Position f)
+ { setProperty(CssFloat, f); }
+ inline Position position() const
+ { return static_cast<Position>(intProperty(CssFloat)); }
+
+ inline void setBorder(qreal border);
+ inline qreal border() const
+ { return doubleProperty(FrameBorder); }
+
+ inline void setBorderBrush(const QBrush &brush)
+ { setProperty(FrameBorderBrush, brush); }
+ inline QBrush borderBrush() const
+ { return brushProperty(FrameBorderBrush); }
+
+ inline void setBorderStyle(BorderStyle style)
+ { setProperty(FrameBorderStyle, style); }
+ inline BorderStyle borderStyle() const
+ { return static_cast<BorderStyle>(intProperty(FrameBorderStyle)); }
+
+ void setMargin(qreal margin);
+ inline qreal margin() const
+ { return doubleProperty(FrameMargin); }
+
+ inline void setTopMargin(qreal margin);
+ qreal topMargin() const;
+
+ inline void setBottomMargin(qreal margin);
+ qreal bottomMargin() const;
+
+ inline void setLeftMargin(qreal margin);
+ qreal leftMargin() const;
+
+ inline void setRightMargin(qreal margin);
+ qreal rightMargin() const;
+
+ inline void setPadding(qreal padding);
+ inline qreal padding() const
+ { return doubleProperty(FramePadding); }
+
+ inline void setWidth(qreal width);
+ inline void setWidth(const QTextLength &length)
+ { setProperty(FrameWidth, length); }
+ inline QTextLength width() const
+ { return lengthProperty(FrameWidth); }
+
+ inline void setHeight(qreal height);
+ inline void setHeight(const QTextLength &height);
+ inline QTextLength height() const
+ { return lengthProperty(FrameHeight); }
+
+ inline void setPageBreakPolicy(PageBreakFlags flags)
+ { setProperty(PageBreakPolicy, int(flags)); }
+ inline PageBreakFlags pageBreakPolicy() const
+ { return PageBreakFlags(intProperty(PageBreakPolicy)); }
+
+protected:
+ explicit QTextFrameFormat(const QTextFormat &fmt);
+ friend class QTextFormat;
+};
+
+inline void QTextFrameFormat::setBorder(qreal aborder)
+{ setProperty(FrameBorder, aborder); }
+
+inline void QTextFrameFormat::setPadding(qreal apadding)
+{ setProperty(FramePadding, apadding); }
+
+inline void QTextFrameFormat::setWidth(qreal awidth)
+{ setProperty(FrameWidth, QTextLength(QTextLength::FixedLength, awidth)); }
+
+inline void QTextFrameFormat::setHeight(qreal aheight)
+{ setProperty(FrameHeight, QTextLength(QTextLength::FixedLength, aheight)); }
+inline void QTextFrameFormat::setHeight(const QTextLength &aheight)
+{ setProperty(FrameHeight, aheight); }
+
+inline void QTextFrameFormat::setTopMargin(qreal amargin)
+{ setProperty(FrameTopMargin, amargin); }
+
+inline void QTextFrameFormat::setBottomMargin(qreal amargin)
+{ setProperty(FrameBottomMargin, amargin); }
+
+inline void QTextFrameFormat::setLeftMargin(qreal amargin)
+{ setProperty(FrameLeftMargin, amargin); }
+
+inline void QTextFrameFormat::setRightMargin(qreal amargin)
+{ setProperty(FrameRightMargin, amargin); }
+
+class Q_GUI_EXPORT QTextTableFormat : public QTextFrameFormat
+{
+public:
+ QTextTableFormat();
+
+ inline bool isValid() const { return isTableFormat(); }
+
+ inline int columns() const
+ { int cols = intProperty(TableColumns); if (cols == 0) cols = 1; return cols; }
+ inline void setColumns(int columns);
+
+ inline void setColumnWidthConstraints(const QVector<QTextLength> &constraints)
+ { setProperty(TableColumnWidthConstraints, constraints); }
+
+ inline QVector<QTextLength> columnWidthConstraints() const
+ { return lengthVectorProperty(TableColumnWidthConstraints); }
+
+ inline void clearColumnWidthConstraints()
+ { clearProperty(TableColumnWidthConstraints); }
+
+ inline qreal cellSpacing() const
+ { return doubleProperty(TableCellSpacing); }
+ inline void setCellSpacing(qreal spacing)
+ { setProperty(TableCellSpacing, spacing); }
+
+ inline qreal cellPadding() const
+ { return doubleProperty(TableCellPadding); }
+ inline void setCellPadding(qreal padding);
+
+ inline void setAlignment(Qt::Alignment alignment);
+ inline Qt::Alignment alignment() const
+ { return QFlag(intProperty(BlockAlignment)); }
+
+ inline void setHeaderRowCount(int count)
+ { setProperty(TableHeaderRowCount, count); }
+ inline int headerRowCount() const
+ { return intProperty(TableHeaderRowCount); }
+
+protected:
+ explicit QTextTableFormat(const QTextFormat &fmt);
+ friend class QTextFormat;
+};
+
+inline void QTextTableFormat::setColumns(int acolumns)
+{
+ if (acolumns == 1)
+ acolumns = 0;
+ setProperty(TableColumns, acolumns);
+}
+
+inline void QTextTableFormat::setCellPadding(qreal apadding)
+{ setProperty(TableCellPadding, apadding); }
+
+inline void QTextTableFormat::setAlignment(Qt::Alignment aalignment)
+{ setProperty(BlockAlignment, int(aalignment)); }
+
+class Q_GUI_EXPORT QTextTableCellFormat : public QTextCharFormat
+{
+public:
+ QTextTableCellFormat();
+
+ inline bool isValid() const { return isTableCellFormat(); }
+
+ inline void setTopPadding(qreal padding);
+ inline qreal topPadding() const;
+
+ inline void setBottomPadding(qreal padding);
+ inline qreal bottomPadding() const;
+
+ inline void setLeftPadding(qreal padding);
+ inline qreal leftPadding() const;
+
+ inline void setRightPadding(qreal padding);
+ inline qreal rightPadding() const;
+
+ inline void setPadding(qreal padding);
+
+protected:
+ explicit QTextTableCellFormat(const QTextFormat &fmt);
+ friend class QTextFormat;
+};
+
+inline void QTextTableCellFormat::setTopPadding(qreal padding)
+{
+ setProperty(TableCellTopPadding, padding);
+}
+
+inline qreal QTextTableCellFormat::topPadding() const
+{
+ return doubleProperty(TableCellTopPadding);
+}
+
+inline void QTextTableCellFormat::setBottomPadding(qreal padding)
+{
+ setProperty(TableCellBottomPadding, padding);
+}
+
+inline qreal QTextTableCellFormat::bottomPadding() const
+{
+ return doubleProperty(TableCellBottomPadding);
+}
+
+inline void QTextTableCellFormat::setLeftPadding(qreal padding)
+{
+ setProperty(TableCellLeftPadding, padding);
+}
+
+inline qreal QTextTableCellFormat::leftPadding() const
+{
+ return doubleProperty(TableCellLeftPadding);
+}
+
+inline void QTextTableCellFormat::setRightPadding(qreal padding)
+{
+ setProperty(TableCellRightPadding, padding);
+}
+
+inline qreal QTextTableCellFormat::rightPadding() const
+{
+ return doubleProperty(TableCellRightPadding);
+}
+
+inline void QTextTableCellFormat::setPadding(qreal padding)
+{
+ setTopPadding(padding);
+ setBottomPadding(padding);
+ setLeftPadding(padding);
+ setRightPadding(padding);
+}
+
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QTEXTFORMAT_H
diff --git a/src/gui/text/qtextformat_p.h b/src/gui/text/qtextformat_p.h
new file mode 100644
index 0000000000..115bc80a00
--- /dev/null
+++ b/src/gui/text/qtextformat_p.h
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTFORMAT_P_H
+#define QTEXTFORMAT_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/qtextformat.h"
+#include "QtCore/qvector.h"
+#include "QtCore/qset.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_GUI_EXPORT QTextFormatCollection
+{
+public:
+ QTextFormatCollection() {}
+ ~QTextFormatCollection();
+
+ QTextFormatCollection(const QTextFormatCollection &rhs);
+ QTextFormatCollection &operator=(const QTextFormatCollection &rhs);
+
+ QTextFormat objectFormat(int objectIndex) const;
+ void setObjectFormat(int objectIndex, const QTextFormat &format);
+
+ int objectFormatIndex(int objectIndex) const;
+ void setObjectFormatIndex(int objectIndex, int formatIndex);
+
+ int createObjectIndex(const QTextFormat &f);
+
+ int indexForFormat(const QTextFormat &f);
+ bool hasFormatCached(const QTextFormat &format) const;
+
+ QTextFormat format(int idx) const;
+ inline QTextBlockFormat blockFormat(int index) const
+ { return format(index).toBlockFormat(); }
+ inline QTextCharFormat charFormat(int index) const
+ { return format(index).toCharFormat(); }
+ inline QTextListFormat listFormat(int index) const
+ { return format(index).toListFormat(); }
+ inline QTextTableFormat tableFormat(int index) const
+ { return format(index).toTableFormat(); }
+ inline QTextImageFormat imageFormat(int index) const
+ { return format(index).toImageFormat(); }
+
+ inline int numFormats() const { return formats.count(); }
+
+ typedef QVector<QTextFormat> FormatVector;
+
+ FormatVector formats;
+ QVector<qint32> objFormats;
+ QSet<uint> hashes;
+
+ inline QFont defaultFont() const { return defaultFnt; }
+ void setDefaultFont(const QFont &f);
+
+private:
+ QFont defaultFnt;
+};
+
+QT_END_NAMESPACE
+
+#endif // QTEXTFORMAT_P_H
diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp
new file mode 100644
index 0000000000..b1f1b75bfa
--- /dev/null
+++ b/src/gui/text/qtexthtmlparser.cpp
@@ -0,0 +1,1881 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtexthtmlparser_p.h"
+
+#include <qbytearray.h>
+#include <qtextcodec.h>
+#include <qapplication.h>
+#include <qstack.h>
+#include <qdebug.h>
+#include <qthread.h>
+
+#include "qtextdocument.h"
+#include "qtextformat_p.h"
+#include "qtextdocument_p.h"
+#include "qtextcursor.h"
+#include "qfont_p.h"
+#include "private/qunicodetables_p.h"
+#include "private/qfunctions_p.h"
+
+#ifndef QT_NO_TEXTHTMLPARSER
+
+QT_BEGIN_NAMESPACE
+
+// see also tst_qtextdocumentfragment.cpp
+#define MAX_ENTITY 258
+static const struct QTextHtmlEntity { const char *name; quint16 code; } entities[MAX_ENTITY]= {
+ { "AElig", 0x00c6 },
+ { "AMP", 38 },
+ { "Aacute", 0x00c1 },
+ { "Acirc", 0x00c2 },
+ { "Agrave", 0x00c0 },
+ { "Alpha", 0x0391 },
+ { "Aring", 0x00c5 },
+ { "Atilde", 0x00c3 },
+ { "Auml", 0x00c4 },
+ { "Beta", 0x0392 },
+ { "Ccedil", 0x00c7 },
+ { "Chi", 0x03a7 },
+ { "Dagger", 0x2021 },
+ { "Delta", 0x0394 },
+ { "ETH", 0x00d0 },
+ { "Eacute", 0x00c9 },
+ { "Ecirc", 0x00ca },
+ { "Egrave", 0x00c8 },
+ { "Epsilon", 0x0395 },
+ { "Eta", 0x0397 },
+ { "Euml", 0x00cb },
+ { "GT", 62 },
+ { "Gamma", 0x0393 },
+ { "Iacute", 0x00cd },
+ { "Icirc", 0x00ce },
+ { "Igrave", 0x00cc },
+ { "Iota", 0x0399 },
+ { "Iuml", 0x00cf },
+ { "Kappa", 0x039a },
+ { "LT", 60 },
+ { "Lambda", 0x039b },
+ { "Mu", 0x039c },
+ { "Ntilde", 0x00d1 },
+ { "Nu", 0x039d },
+ { "OElig", 0x0152 },
+ { "Oacute", 0x00d3 },
+ { "Ocirc", 0x00d4 },
+ { "Ograve", 0x00d2 },
+ { "Omega", 0x03a9 },
+ { "Omicron", 0x039f },
+ { "Oslash", 0x00d8 },
+ { "Otilde", 0x00d5 },
+ { "Ouml", 0x00d6 },
+ { "Phi", 0x03a6 },
+ { "Pi", 0x03a0 },
+ { "Prime", 0x2033 },
+ { "Psi", 0x03a8 },
+ { "QUOT", 34 },
+ { "Rho", 0x03a1 },
+ { "Scaron", 0x0160 },
+ { "Sigma", 0x03a3 },
+ { "THORN", 0x00de },
+ { "Tau", 0x03a4 },
+ { "Theta", 0x0398 },
+ { "Uacute", 0x00da },
+ { "Ucirc", 0x00db },
+ { "Ugrave", 0x00d9 },
+ { "Upsilon", 0x03a5 },
+ { "Uuml", 0x00dc },
+ { "Xi", 0x039e },
+ { "Yacute", 0x00dd },
+ { "Yuml", 0x0178 },
+ { "Zeta", 0x0396 },
+ { "aacute", 0x00e1 },
+ { "acirc", 0x00e2 },
+ { "acute", 0x00b4 },
+ { "aelig", 0x00e6 },
+ { "agrave", 0x00e0 },
+ { "alefsym", 0x2135 },
+ { "alpha", 0x03b1 },
+ { "amp", 38 },
+ { "and", 0x22a5 },
+ { "ang", 0x2220 },
+ { "apos", 0x0027 },
+ { "aring", 0x00e5 },
+ { "asymp", 0x2248 },
+ { "atilde", 0x00e3 },
+ { "auml", 0x00e4 },
+ { "bdquo", 0x201e },
+ { "beta", 0x03b2 },
+ { "brvbar", 0x00a6 },
+ { "bull", 0x2022 },
+ { "cap", 0x2229 },
+ { "ccedil", 0x00e7 },
+ { "cedil", 0x00b8 },
+ { "cent", 0x00a2 },
+ { "chi", 0x03c7 },
+ { "circ", 0x02c6 },
+ { "clubs", 0x2663 },
+ { "cong", 0x2245 },
+ { "copy", 0x00a9 },
+ { "crarr", 0x21b5 },
+ { "cup", 0x222a },
+ { "curren", 0x00a4 },
+ { "dArr", 0x21d3 },
+ { "dagger", 0x2020 },
+ { "darr", 0x2193 },
+ { "deg", 0x00b0 },
+ { "delta", 0x03b4 },
+ { "diams", 0x2666 },
+ { "divide", 0x00f7 },
+ { "eacute", 0x00e9 },
+ { "ecirc", 0x00ea },
+ { "egrave", 0x00e8 },
+ { "empty", 0x2205 },
+ { "emsp", 0x2003 },
+ { "ensp", 0x2002 },
+ { "epsilon", 0x03b5 },
+ { "equiv", 0x2261 },
+ { "eta", 0x03b7 },
+ { "eth", 0x00f0 },
+ { "euml", 0x00eb },
+ { "euro", 0x20ac },
+ { "exist", 0x2203 },
+ { "fnof", 0x0192 },
+ { "forall", 0x2200 },
+ { "frac12", 0x00bd },
+ { "frac14", 0x00bc },
+ { "frac34", 0x00be },
+ { "frasl", 0x2044 },
+ { "gamma", 0x03b3 },
+ { "ge", 0x2265 },
+ { "gt", 62 },
+ { "hArr", 0x21d4 },
+ { "harr", 0x2194 },
+ { "hearts", 0x2665 },
+ { "hellip", 0x2026 },
+ { "iacute", 0x00ed },
+ { "icirc", 0x00ee },
+ { "iexcl", 0x00a1 },
+ { "igrave", 0x00ec },
+ { "image", 0x2111 },
+ { "infin", 0x221e },
+ { "int", 0x222b },
+ { "iota", 0x03b9 },
+ { "iquest", 0x00bf },
+ { "isin", 0x2208 },
+ { "iuml", 0x00ef },
+ { "kappa", 0x03ba },
+ { "lArr", 0x21d0 },
+ { "lambda", 0x03bb },
+ { "lang", 0x2329 },
+ { "laquo", 0x00ab },
+ { "larr", 0x2190 },
+ { "lceil", 0x2308 },
+ { "ldquo", 0x201c },
+ { "le", 0x2264 },
+ { "lfloor", 0x230a },
+ { "lowast", 0x2217 },
+ { "loz", 0x25ca },
+ { "lrm", 0x200e },
+ { "lsaquo", 0x2039 },
+ { "lsquo", 0x2018 },
+ { "lt", 60 },
+ { "macr", 0x00af },
+ { "mdash", 0x2014 },
+ { "micro", 0x00b5 },
+ { "middot", 0x00b7 },
+ { "minus", 0x2212 },
+ { "mu", 0x03bc },
+ { "nabla", 0x2207 },
+ { "nbsp", 0x00a0 },
+ { "ndash", 0x2013 },
+ { "ne", 0x2260 },
+ { "ni", 0x220b },
+ { "not", 0x00ac },
+ { "notin", 0x2209 },
+ { "nsub", 0x2284 },
+ { "ntilde", 0x00f1 },
+ { "nu", 0x03bd },
+ { "oacute", 0x00f3 },
+ { "ocirc", 0x00f4 },
+ { "oelig", 0x0153 },
+ { "ograve", 0x00f2 },
+ { "oline", 0x203e },
+ { "omega", 0x03c9 },
+ { "omicron", 0x03bf },
+ { "oplus", 0x2295 },
+ { "or", 0x22a6 },
+ { "ordf", 0x00aa },
+ { "ordm", 0x00ba },
+ { "oslash", 0x00f8 },
+ { "otilde", 0x00f5 },
+ { "otimes", 0x2297 },
+ { "ouml", 0x00f6 },
+ { "para", 0x00b6 },
+ { "part", 0x2202 },
+ { "percnt", 0x0025 },
+ { "permil", 0x2030 },
+ { "perp", 0x22a5 },
+ { "phi", 0x03c6 },
+ { "pi", 0x03c0 },
+ { "piv", 0x03d6 },
+ { "plusmn", 0x00b1 },
+ { "pound", 0x00a3 },
+ { "prime", 0x2032 },
+ { "prod", 0x220f },
+ { "prop", 0x221d },
+ { "psi", 0x03c8 },
+ { "quot", 34 },
+ { "rArr", 0x21d2 },
+ { "radic", 0x221a },
+ { "rang", 0x232a },
+ { "raquo", 0x00bb },
+ { "rarr", 0x2192 },
+ { "rceil", 0x2309 },
+ { "rdquo", 0x201d },
+ { "real", 0x211c },
+ { "reg", 0x00ae },
+ { "rfloor", 0x230b },
+ { "rho", 0x03c1 },
+ { "rlm", 0x200f },
+ { "rsaquo", 0x203a },
+ { "rsquo", 0x2019 },
+ { "sbquo", 0x201a },
+ { "scaron", 0x0161 },
+ { "sdot", 0x22c5 },
+ { "sect", 0x00a7 },
+ { "shy", 0x00ad },
+ { "sigma", 0x03c3 },
+ { "sigmaf", 0x03c2 },
+ { "sim", 0x223c },
+ { "spades", 0x2660 },
+ { "sub", 0x2282 },
+ { "sube", 0x2286 },
+ { "sum", 0x2211 },
+ { "sup", 0x2283 },
+ { "sup1", 0x00b9 },
+ { "sup2", 0x00b2 },
+ { "sup3", 0x00b3 },
+ { "supe", 0x2287 },
+ { "szlig", 0x00df },
+ { "tau", 0x03c4 },
+ { "there4", 0x2234 },
+ { "theta", 0x03b8 },
+ { "thetasym", 0x03d1 },
+ { "thinsp", 0x2009 },
+ { "thorn", 0x00fe },
+ { "tilde", 0x02dc },
+ { "times", 0x00d7 },
+ { "trade", 0x2122 },
+ { "uArr", 0x21d1 },
+ { "uacute", 0x00fa },
+ { "uarr", 0x2191 },
+ { "ucirc", 0x00fb },
+ { "ugrave", 0x00f9 },
+ { "uml", 0x00a8 },
+ { "upsih", 0x03d2 },
+ { "upsilon", 0x03c5 },
+ { "uuml", 0x00fc },
+ { "weierp", 0x2118 },
+ { "xi", 0x03be },
+ { "yacute", 0x00fd },
+ { "yen", 0x00a5 },
+ { "yuml", 0x00ff },
+ { "zeta", 0x03b6 },
+ { "zwj", 0x200d },
+ { "zwnj", 0x200c }
+};
+
+Q_STATIC_GLOBAL_OPERATOR bool operator<(const QString &entityStr, const QTextHtmlEntity &entity)
+{
+ return entityStr < QLatin1String(entity.name);
+}
+
+Q_STATIC_GLOBAL_OPERATOR bool operator<(const QTextHtmlEntity &entity, const QString &entityStr)
+{
+ return QLatin1String(entity.name) < entityStr;
+}
+
+static QChar resolveEntity(const QString &entity)
+{
+ const QTextHtmlEntity *start = &entities[0];
+ const QTextHtmlEntity *end = &entities[MAX_ENTITY];
+ const QTextHtmlEntity *e = qBinaryFind(start, end, entity);
+ if (e == end)
+ return QChar();
+ return e->code;
+}
+
+static const uint windowsLatin1ExtendedCharacters[0xA0 - 0x80] = {
+ 0x20ac, // 0x80
+ 0x0081, // 0x81 direct mapping
+ 0x201a, // 0x82
+ 0x0192, // 0x83
+ 0x201e, // 0x84
+ 0x2026, // 0x85
+ 0x2020, // 0x86
+ 0x2021, // 0x87
+ 0x02C6, // 0x88
+ 0x2030, // 0x89
+ 0x0160, // 0x8A
+ 0x2039, // 0x8B
+ 0x0152, // 0x8C
+ 0x008D, // 0x8D direct mapping
+ 0x017D, // 0x8E
+ 0x008F, // 0x8F directmapping
+ 0x0090, // 0x90 directmapping
+ 0x2018, // 0x91
+ 0x2019, // 0x92
+ 0x201C, // 0x93
+ 0X201D, // 0x94
+ 0x2022, // 0x95
+ 0x2013, // 0x96
+ 0x2014, // 0x97
+ 0x02DC, // 0x98
+ 0x2122, // 0x99
+ 0x0161, // 0x9A
+ 0x203A, // 0x9B
+ 0x0153, // 0x9C
+ 0x009D, // 0x9D direct mapping
+ 0x017E, // 0x9E
+ 0x0178 // 0x9F
+};
+
+// the displayMode value is according to the what are blocks in the piecetable, not
+// what the w3c defines.
+static const QTextHtmlElement elements[Html_NumElements]= {
+ { "a", Html_a, QTextHtmlElement::DisplayInline },
+ { "address", Html_address, QTextHtmlElement::DisplayInline },
+ { "b", Html_b, QTextHtmlElement::DisplayInline },
+ { "big", Html_big, QTextHtmlElement::DisplayInline },
+ { "blockquote", Html_blockquote, QTextHtmlElement::DisplayBlock },
+ { "body", Html_body, QTextHtmlElement::DisplayBlock },
+ { "br", Html_br, QTextHtmlElement::DisplayInline },
+ { "caption", Html_caption, QTextHtmlElement::DisplayBlock },
+ { "center", Html_center, QTextHtmlElement::DisplayBlock },
+ { "cite", Html_cite, QTextHtmlElement::DisplayInline },
+ { "code", Html_code, QTextHtmlElement::DisplayInline },
+ { "dd", Html_dd, QTextHtmlElement::DisplayBlock },
+ { "dfn", Html_dfn, QTextHtmlElement::DisplayInline },
+ { "div", Html_div, QTextHtmlElement::DisplayBlock },
+ { "dl", Html_dl, QTextHtmlElement::DisplayBlock },
+ { "dt", Html_dt, QTextHtmlElement::DisplayBlock },
+ { "em", Html_em, QTextHtmlElement::DisplayInline },
+ { "font", Html_font, QTextHtmlElement::DisplayInline },
+ { "h1", Html_h1, QTextHtmlElement::DisplayBlock },
+ { "h2", Html_h2, QTextHtmlElement::DisplayBlock },
+ { "h3", Html_h3, QTextHtmlElement::DisplayBlock },
+ { "h4", Html_h4, QTextHtmlElement::DisplayBlock },
+ { "h5", Html_h5, QTextHtmlElement::DisplayBlock },
+ { "h6", Html_h6, QTextHtmlElement::DisplayBlock },
+ { "head", Html_head, QTextHtmlElement::DisplayNone },
+ { "hr", Html_hr, QTextHtmlElement::DisplayBlock },
+ { "html", Html_html, QTextHtmlElement::DisplayInline },
+ { "i", Html_i, QTextHtmlElement::DisplayInline },
+ { "img", Html_img, QTextHtmlElement::DisplayInline },
+ { "kbd", Html_kbd, QTextHtmlElement::DisplayInline },
+ { "li", Html_li, QTextHtmlElement::DisplayBlock },
+ { "link", Html_link, QTextHtmlElement::DisplayNone },
+ { "meta", Html_meta, QTextHtmlElement::DisplayNone },
+ { "nobr", Html_nobr, QTextHtmlElement::DisplayInline },
+ { "ol", Html_ol, QTextHtmlElement::DisplayBlock },
+ { "p", Html_p, QTextHtmlElement::DisplayBlock },
+ { "pre", Html_pre, QTextHtmlElement::DisplayBlock },
+ { "qt", Html_body /*deliberate mapping*/, QTextHtmlElement::DisplayBlock },
+ { "s", Html_s, QTextHtmlElement::DisplayInline },
+ { "samp", Html_samp, QTextHtmlElement::DisplayInline },
+ { "script", Html_script, QTextHtmlElement::DisplayNone },
+ { "small", Html_small, QTextHtmlElement::DisplayInline },
+ { "span", Html_span, QTextHtmlElement::DisplayInline },
+ { "strong", Html_strong, QTextHtmlElement::DisplayInline },
+ { "style", Html_style, QTextHtmlElement::DisplayNone },
+ { "sub", Html_sub, QTextHtmlElement::DisplayInline },
+ { "sup", Html_sup, QTextHtmlElement::DisplayInline },
+ { "table", Html_table, QTextHtmlElement::DisplayTable },
+ { "tbody", Html_tbody, QTextHtmlElement::DisplayTable },
+ { "td", Html_td, QTextHtmlElement::DisplayBlock },
+ { "tfoot", Html_tfoot, QTextHtmlElement::DisplayTable },
+ { "th", Html_th, QTextHtmlElement::DisplayBlock },
+ { "thead", Html_thead, QTextHtmlElement::DisplayTable },
+ { "title", Html_title, QTextHtmlElement::DisplayNone },
+ { "tr", Html_tr, QTextHtmlElement::DisplayTable },
+ { "tt", Html_tt, QTextHtmlElement::DisplayInline },
+ { "u", Html_u, QTextHtmlElement::DisplayInline },
+ { "ul", Html_ul, QTextHtmlElement::DisplayBlock },
+ { "var", Html_var, QTextHtmlElement::DisplayInline },
+};
+
+
+Q_STATIC_GLOBAL_OPERATOR bool operator<(const QString &str, const QTextHtmlElement &e)
+{
+ return str < QLatin1String(e.name);
+}
+
+Q_STATIC_GLOBAL_OPERATOR bool operator<(const QTextHtmlElement &e, const QString &str)
+{
+ return QLatin1String(e.name) < str;
+}
+
+static const QTextHtmlElement *lookupElementHelper(const QString &element)
+{
+ const QTextHtmlElement *start = &elements[0];
+ const QTextHtmlElement *end = &elements[Html_NumElements];
+ const QTextHtmlElement *e = qBinaryFind(start, end, element);
+ if (e == end)
+ return 0;
+ return e;
+}
+
+int QTextHtmlParser::lookupElement(const QString &element)
+{
+ const QTextHtmlElement *e = lookupElementHelper(element);
+ if (!e)
+ return -1;
+ return e->id;
+}
+
+// quotes newlines as "\\n"
+static QString quoteNewline(const QString &s)
+{
+ QString n = s;
+ n.replace(QLatin1Char('\n'), QLatin1String("\\n"));
+ return n;
+}
+
+QTextHtmlParserNode::QTextHtmlParserNode()
+ : parent(0), id(Html_unknown),
+ cssFloat(QTextFrameFormat::InFlow), hasOwnListStyle(false),
+ hasCssListIndent(false), isEmptyParagraph(false), isTextFrame(false), isRootFrame(false),
+ displayMode(QTextHtmlElement::DisplayInline), hasHref(false),
+ listStyle(QTextListFormat::ListStyleUndefined), imageWidth(-1), imageHeight(-1), tableBorder(0),
+ tableCellRowSpan(1), tableCellColSpan(1), tableCellSpacing(2), tableCellPadding(0),
+ borderBrush(Qt::darkGray), borderStyle(QTextFrameFormat::BorderStyle_Outset),
+ userState(-1), cssListIndent(0), wsm(WhiteSpaceModeUndefined)
+{
+ margin[QTextHtmlParser::MarginLeft] = 0;
+ margin[QTextHtmlParser::MarginRight] = 0;
+ margin[QTextHtmlParser::MarginTop] = 0;
+ margin[QTextHtmlParser::MarginBottom] = 0;
+}
+
+void QTextHtmlParser::dumpHtml()
+{
+ for (int i = 0; i < count(); ++i) {
+ qDebug().nospace() << qPrintable(QString(depth(i)*4, QLatin1Char(' ')))
+ << qPrintable(at(i).tag) << ":"
+ << quoteNewline(at(i).text);
+ ;
+ }
+}
+
+QTextHtmlParserNode *QTextHtmlParser::newNode(int parent)
+{
+ QTextHtmlParserNode *lastNode = &nodes.last();
+ QTextHtmlParserNode *newNode = 0;
+
+ bool reuseLastNode = true;
+
+ if (nodes.count() == 1) {
+ reuseLastNode = false;
+ } else if (lastNode->tag.isEmpty()) {
+
+ if (lastNode->text.isEmpty()) {
+ reuseLastNode = true;
+ } else { // last node is a text node (empty tag) with some text
+
+ if (lastNode->text.length() == 1 && lastNode->text.at(0).isSpace()) {
+
+ int lastSibling = count() - 2;
+ while (lastSibling
+ && at(lastSibling).parent != lastNode->parent
+ && at(lastSibling).displayMode == QTextHtmlElement::DisplayInline) {
+ lastSibling = at(lastSibling).parent;
+ }
+
+ if (at(lastSibling).displayMode == QTextHtmlElement::DisplayInline) {
+ reuseLastNode = false;
+ } else {
+ reuseLastNode = true;
+ }
+ } else {
+ // text node with real (non-whitespace) text -> nothing to re-use
+ reuseLastNode = false;
+ }
+
+ }
+
+ } else {
+ // last node had a proper tag -> nothing to re-use
+ reuseLastNode = false;
+ }
+
+ if (reuseLastNode) {
+ newNode = lastNode;
+ newNode->tag.clear();
+ newNode->text.clear();
+ newNode->id = Html_unknown;
+ } else {
+ nodes.resize(nodes.size() + 1);
+ newNode = &nodes.last();
+ }
+
+ newNode->parent = parent;
+ return newNode;
+}
+
+void QTextHtmlParser::parse(const QString &text, const QTextDocument *_resourceProvider)
+{
+ nodes.clear();
+ nodes.resize(1);
+ txt = text;
+ pos = 0;
+ len = txt.length();
+ textEditMode = false;
+ resourceProvider = _resourceProvider;
+ parse();
+ //dumpHtml();
+}
+
+int QTextHtmlParser::depth(int i) const
+{
+ int depth = 0;
+ while (i) {
+ i = at(i).parent;
+ ++depth;
+ }
+ return depth;
+}
+
+int QTextHtmlParser::margin(int i, int mar) const {
+ int m = 0;
+ const QTextHtmlParserNode *node;
+ if (mar == MarginLeft
+ || mar == MarginRight) {
+ while (i) {
+ node = &at(i);
+ if (!node->isBlock() && node->id != Html_table)
+ break;
+ if (node->isTableCell())
+ break;
+ m += node->margin[mar];
+ i = node->parent;
+ }
+ }
+ return m;
+}
+
+int QTextHtmlParser::topMargin(int i) const
+{
+ if (!i)
+ return 0;
+ return at(i).margin[MarginTop];
+}
+
+int QTextHtmlParser::bottomMargin(int i) const
+{
+ if (!i)
+ return 0;
+ return at(i).margin[MarginBottom];
+}
+
+void QTextHtmlParser::eatSpace()
+{
+ while (pos < len && txt.at(pos).isSpace() && txt.at(pos) != QChar::ParagraphSeparator)
+ pos++;
+}
+
+void QTextHtmlParser::parse()
+{
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c == QLatin1Char('<')) {
+ parseTag();
+ } else if (c == QLatin1Char('&')) {
+ nodes.last().text += parseEntity();
+ } else {
+ nodes.last().text += c;
+ }
+ }
+}
+
+// parses a tag after "<"
+void QTextHtmlParser::parseTag()
+{
+ eatSpace();
+
+ // handle comments and other exclamation mark declarations
+ if (hasPrefix(QLatin1Char('!'))) {
+ parseExclamationTag();
+ if (nodes.last().wsm != QTextHtmlParserNode::WhiteSpacePre
+ && nodes.last().wsm != QTextHtmlParserNode::WhiteSpacePreWrap
+ && !textEditMode)
+ eatSpace();
+ return;
+ }
+
+ // if close tag just close
+ if (hasPrefix(QLatin1Char('/'))) {
+ if (nodes.last().id == Html_style) {
+#ifndef QT_NO_CSSPARSER
+ QCss::Parser parser(nodes.last().text);
+ QCss::StyleSheet sheet;
+ sheet.origin = QCss::StyleSheetOrigin_Author;
+ parser.parse(&sheet, Qt::CaseInsensitive);
+ inlineStyleSheets.append(sheet);
+ resolveStyleSheetImports(sheet);
+#endif
+ }
+ parseCloseTag();
+ return;
+ }
+
+ int p = last();
+ while (p && at(p).tag.size() == 0)
+ p = at(p).parent;
+
+ QTextHtmlParserNode *node = newNode(p);
+
+ // parse tag name
+ node->tag = parseWord().toLower();
+
+ const QTextHtmlElement *elem = lookupElementHelper(node->tag);
+ if (elem) {
+ node->id = elem->id;
+ node->displayMode = elem->displayMode;
+ } else {
+ node->id = Html_unknown;
+ }
+
+ node->attributes.clear();
+ // _need_ at least one space after the tag name, otherwise there can't be attributes
+ if (pos < len && txt.at(pos).isSpace())
+ node->attributes = parseAttributes();
+
+ // resolveParent() may have to change the order in the tree and
+ // insert intermediate nodes for buggy HTML, so re-initialize the 'node'
+ // pointer through the return value
+ node = resolveParent();
+ resolveNode();
+
+ const int nodeIndex = nodes.count() - 1; // this new node is always the last
+#ifndef QT_NO_CSSPARSER
+ node->applyCssDeclarations(declarationsForNode(nodeIndex), resourceProvider);
+#endif
+ applyAttributes(node->attributes);
+
+ // finish tag
+ bool tagClosed = false;
+ while (pos < len && txt.at(pos) != QLatin1Char('>')) {
+ if (txt.at(pos) == QLatin1Char('/'))
+ tagClosed = true;
+
+ pos++;
+ }
+ pos++;
+
+ // in a white-space preserving environment strip off a initial newline
+ // since the element itself already generates a newline
+ if ((node->wsm == QTextHtmlParserNode::WhiteSpacePre
+ || node->wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
+ && node->isBlock()) {
+ if (pos < len - 1 && txt.at(pos) == QLatin1Char('\n'))
+ ++pos;
+ }
+
+ if (node->mayNotHaveChildren() || tagClosed) {
+ newNode(node->parent);
+ resolveNode();
+ }
+}
+
+// parses a tag beginning with "/"
+void QTextHtmlParser::parseCloseTag()
+{
+ ++pos;
+ QString tag = parseWord().toLower().trimmed();
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c == QLatin1Char('>'))
+ break;
+ }
+
+ // find corresponding open node
+ int p = last();
+ if (p > 0
+ && at(p - 1).tag == tag
+ && at(p - 1).mayNotHaveChildren())
+ p--;
+
+ while (p && at(p).tag != tag)
+ p = at(p).parent;
+
+ // simply ignore the tag if we can't find
+ // a corresponding open node, for broken
+ // html such as <font>blah</font></font>
+ if (!p)
+ return;
+
+ // in a white-space preserving environment strip off a trailing newline
+ // since the closing of the opening block element will automatically result
+ // in a new block for elements following the <pre>
+ // ...foo\n</pre><p>blah -> foo</pre><p>blah
+ if ((at(p).wsm == QTextHtmlParserNode::WhiteSpacePre
+ || at(p).wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
+ && at(p).isBlock()) {
+ if (at(last()).text.endsWith(QLatin1Char('\n')))
+ nodes[last()].text.chop(1);
+ }
+
+ newNode(at(p).parent);
+ resolveNode();
+}
+
+// parses a tag beginning with "!"
+void QTextHtmlParser::parseExclamationTag()
+{
+ ++pos;
+ if (hasPrefix(QLatin1Char('-'),1) && hasPrefix(QLatin1Char('-'),2)) {
+ pos += 3;
+ // eat comments
+ int end = txt.indexOf(QLatin1String("-->"), pos);
+ pos = (end >= 0 ? end + 3 : len);
+ } else {
+ // eat internal tags
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c == QLatin1Char('>'))
+ break;
+ }
+ }
+}
+
+// parses an entity after "&", and returns it
+QString QTextHtmlParser::parseEntity()
+{
+ int recover = pos;
+ QString entity;
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c.isSpace() || pos - recover > 9) {
+ goto error;
+ }
+ if (c == QLatin1Char(';'))
+ break;
+ entity += c;
+ }
+ {
+ QChar resolved = resolveEntity(entity);
+ if (!resolved.isNull())
+ return QString(resolved);
+ }
+ if (entity.length() > 1 && entity.at(0) == QLatin1Char('#')) {
+ entity.remove(0, 1); // removing leading #
+
+ int base = 10;
+ bool ok = false;
+
+ if (entity.at(0).toLower() == QLatin1Char('x')) { // hex entity?
+ entity.remove(0, 1);
+ base = 16;
+ }
+
+ uint uc = entity.toUInt(&ok, base);
+ if (ok) {
+ if (uc >= 0x80 && uc < 0x80 + (sizeof(windowsLatin1ExtendedCharacters)/sizeof(windowsLatin1ExtendedCharacters[0])))
+ uc = windowsLatin1ExtendedCharacters[uc - 0x80];
+ QString str;
+ if (uc > 0xffff) {
+ // surrogate pair
+ uc -= 0x10000;
+ ushort high = uc/0x400 + 0xd800;
+ ushort low = uc%0x400 + 0xdc00;
+ str.append(QChar(high));
+ str.append(QChar(low));
+ } else {
+ str.append(QChar(uc));
+ }
+ return str;
+ }
+ }
+error:
+ pos = recover;
+ return QLatin1String("&");
+}
+
+// parses one word, possibly quoted, and returns it
+QString QTextHtmlParser::parseWord()
+{
+ QString word;
+ if (hasPrefix(QLatin1Char('\"'))) { // double quotes
+ ++pos;
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c == QLatin1Char('\"'))
+ break;
+ else if (c == QLatin1Char('&'))
+ word += parseEntity();
+ else
+ word += c;
+ }
+ } else if (hasPrefix(QLatin1Char('\''))) { // single quotes
+ ++pos;
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c == QLatin1Char('\''))
+ break;
+ else
+ word += c;
+ }
+ } else { // normal text
+ while (pos < len) {
+ QChar c = txt.at(pos++);
+ if (c == QLatin1Char('>')
+ || (c == QLatin1Char('/') && hasPrefix(QLatin1Char('>'), 1))
+ || c == QLatin1Char('<')
+ || c == QLatin1Char('=')
+ || c.isSpace()) {
+ --pos;
+ break;
+ }
+ if (c == QLatin1Char('&'))
+ word += parseEntity();
+ else
+ word += c;
+ }
+ }
+ return word;
+}
+
+// gives the new node the right parent
+QTextHtmlParserNode *QTextHtmlParser::resolveParent()
+{
+ QTextHtmlParserNode *node = &nodes.last();
+
+ int p = node->parent;
+
+ // Excel gives us buggy HTML with just tr without surrounding table tags
+ // or with just td tags
+
+ if (node->id == Html_td) {
+ int n = p;
+ while (n && at(n).id != Html_tr)
+ n = at(n).parent;
+
+ if (!n) {
+ nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
+ nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
+
+ QTextHtmlParserNode *table = &nodes[nodes.count() - 3];
+ table->parent = p;
+ table->id = Html_table;
+ table->tag = QLatin1String("table");
+ table->children.append(nodes.count() - 2); // add row as child
+
+ QTextHtmlParserNode *row = &nodes[nodes.count() - 2];
+ row->parent = nodes.count() - 3; // table as parent
+ row->id = Html_tr;
+ row->tag = QLatin1String("tr");
+
+ p = nodes.count() - 2;
+ node = &nodes.last(); // re-initialize pointer
+ }
+ }
+
+ if (node->id == Html_tr) {
+ int n = p;
+ while (n && at(n).id != Html_table)
+ n = at(n).parent;
+
+ if (!n) {
+ nodes.insert(nodes.count() - 1, QTextHtmlParserNode());
+ QTextHtmlParserNode *table = &nodes[nodes.count() - 2];
+ table->parent = p;
+ table->id = Html_table;
+ table->tag = QLatin1String("table");
+ p = nodes.count() - 2;
+ node = &nodes.last(); // re-initialize pointer
+ }
+ }
+
+ // permit invalid html by letting block elements be children
+ // of inline elements with the exception of paragraphs:
+ //
+ // a new paragraph closes parent inline elements (while loop),
+ // unless they themselves are children of a non-paragraph block
+ // element (if statement)
+ //
+ // For example:
+ //
+ // <body><p><b>Foo<p>Bar <-- second <p> implicitly closes <b> that
+ // belongs to the first <p>. The self-nesting
+ // check further down prevents the second <p>
+ // from nesting into the first one then.
+ // so Bar is not bold.
+ //
+ // <body><b><p>Foo <-- Foo should be bold.
+ //
+ // <body><b><p>Foo<p>Bar <-- Foo and Bar should be bold.
+ //
+ if (node->id == Html_p) {
+ while (p && !at(p).isBlock())
+ p = at(p).parent;
+
+ if (!p || at(p).id != Html_p)
+ p = node->parent;
+ }
+
+ // some elements are not self nesting
+ if (node->id == at(p).id
+ && node->isNotSelfNesting())
+ p = at(p).parent;
+
+ // some elements are not allowed in certain contexts
+ while ((p && !node->allowedInContext(at(p).id))
+ // ### make new styles aware of empty tags
+ || at(p).mayNotHaveChildren()
+ ) {
+ p = at(p).parent;
+ }
+
+ node->parent = p;
+
+ // makes it easier to traverse the tree, later
+ nodes[p].children.append(nodes.count() - 1);
+ return node;
+}
+
+// sets all properties on the new node
+void QTextHtmlParser::resolveNode()
+{
+ QTextHtmlParserNode *node = &nodes.last();
+ const QTextHtmlParserNode *parent = &nodes.at(node->parent);
+ node->initializeProperties(parent, this);
+}
+
+bool QTextHtmlParserNode::isNestedList(const QTextHtmlParser *parser) const
+{
+ if (!isListStart())
+ return false;
+
+ int p = parent;
+ while (p) {
+ if (parser->at(p).isListStart())
+ return true;
+ p = parser->at(p).parent;
+ }
+ return false;
+}
+
+void QTextHtmlParserNode::initializeProperties(const QTextHtmlParserNode *parent, const QTextHtmlParser *parser)
+{
+ // inherit properties from parent element
+ charFormat = parent->charFormat;
+
+ if (id == Html_html)
+ blockFormat.setLayoutDirection(Qt::LeftToRight); // HTML default
+ else if (parent->blockFormat.hasProperty(QTextFormat::LayoutDirection))
+ blockFormat.setLayoutDirection(parent->blockFormat.layoutDirection());
+
+ if (parent->displayMode == QTextHtmlElement::DisplayNone)
+ displayMode = QTextHtmlElement::DisplayNone;
+
+ if (parent->id != Html_table || id == Html_caption) {
+ if (parent->blockFormat.hasProperty(QTextFormat::BlockAlignment))
+ blockFormat.setAlignment(parent->blockFormat.alignment());
+ else
+ blockFormat.clearProperty(QTextFormat::BlockAlignment);
+ }
+ // we don't paint per-row background colors, yet. so as an
+ // exception inherit the background color here
+ // we also inherit the background between inline elements
+ if ((parent->id != Html_tr || !isTableCell())
+ && (displayMode != QTextHtmlElement::DisplayInline || parent->displayMode != QTextHtmlElement::DisplayInline)) {
+ charFormat.clearProperty(QTextFormat::BackgroundBrush);
+ }
+
+ listStyle = parent->listStyle;
+ // makes no sense to inherit that property, a named anchor is a single point
+ // in the document, which is set by the DocumentFragment
+ charFormat.clearProperty(QTextFormat::AnchorName);
+ wsm = parent->wsm;
+
+ // initialize remaining properties
+ margin[QTextHtmlParser::MarginLeft] = 0;
+ margin[QTextHtmlParser::MarginRight] = 0;
+ margin[QTextHtmlParser::MarginTop] = 0;
+ margin[QTextHtmlParser::MarginBottom] = 0;
+ cssFloat = QTextFrameFormat::InFlow;
+
+ for (int i = 0; i < 4; ++i)
+ padding[i] = -1;
+
+ // set element specific attributes
+ switch (id) {
+ case Html_a:
+ charFormat.setAnchor(true);
+ for (int i = 0; i < attributes.count(); i += 2) {
+ const QString key = attributes.at(i);
+ if (key.compare(QLatin1String("href"), Qt::CaseInsensitive) == 0
+ && !attributes.at(i + 1).isEmpty()) {
+ hasHref = true;
+ charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
+ charFormat.setForeground(QApplication::palette().link());
+ }
+ }
+
+ break;
+ case Html_em:
+ case Html_i:
+ case Html_cite:
+ case Html_address:
+ case Html_var:
+ case Html_dfn:
+ charFormat.setFontItalic(true);
+ break;
+ case Html_big:
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1));
+ break;
+ case Html_small:
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1));
+ break;
+ case Html_strong:
+ case Html_b:
+ charFormat.setFontWeight(QFont::Bold);
+ break;
+ case Html_h1:
+ charFormat.setFontWeight(QFont::Bold);
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(3));
+ margin[QTextHtmlParser::MarginTop] = 18;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ break;
+ case Html_h2:
+ charFormat.setFontWeight(QFont::Bold);
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(2));
+ margin[QTextHtmlParser::MarginTop] = 16;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ break;
+ case Html_h3:
+ charFormat.setFontWeight(QFont::Bold);
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(1));
+ margin[QTextHtmlParser::MarginTop] = 14;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ break;
+ case Html_h4:
+ charFormat.setFontWeight(QFont::Bold);
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(0));
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ break;
+ case Html_h5:
+ charFormat.setFontWeight(QFont::Bold);
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, int(-1));
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 4;
+ break;
+ case Html_p:
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ break;
+ case Html_center:
+ blockFormat.setAlignment(Qt::AlignCenter);
+ break;
+ case Html_ul:
+ listStyle = QTextListFormat::ListDisc;
+ // nested lists don't have margins, except for the toplevel one
+ if (!isNestedList(parser)) {
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ }
+ // no left margin as we use indenting instead
+ break;
+ case Html_ol:
+ listStyle = QTextListFormat::ListDecimal;
+ // nested lists don't have margins, except for the toplevel one
+ if (!isNestedList(parser)) {
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ }
+ // no left margin as we use indenting instead
+ break;
+ case Html_code:
+ case Html_tt:
+ case Html_kbd:
+ case Html_samp:
+ charFormat.setFontFamily(QString::fromLatin1("Courier New,courier"));
+ // <tt> uses a fixed font, so set the property
+ charFormat.setFontFixedPitch(true);
+ break;
+ case Html_br:
+ text = QChar(QChar::LineSeparator);
+ wsm = QTextHtmlParserNode::WhiteSpacePre;
+ break;
+ // ##### sub / sup
+ case Html_pre:
+ charFormat.setFontFamily(QString::fromLatin1("Courier New,courier"));
+ wsm = WhiteSpacePre;
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ // <pre> uses a fixed font
+ charFormat.setFontFixedPitch(true);
+ break;
+ case Html_blockquote:
+ margin[QTextHtmlParser::MarginTop] = 12;
+ margin[QTextHtmlParser::MarginBottom] = 12;
+ margin[QTextHtmlParser::MarginLeft] = 40;
+ margin[QTextHtmlParser::MarginRight] = 40;
+ break;
+ case Html_dl:
+ margin[QTextHtmlParser::MarginTop] = 8;
+ margin[QTextHtmlParser::MarginBottom] = 8;
+ break;
+ case Html_dd:
+ margin[QTextHtmlParser::MarginLeft] = 30;
+ break;
+ case Html_u:
+ charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
+ break;
+ case Html_s:
+ charFormat.setFontStrikeOut(true);
+ break;
+ case Html_nobr:
+ wsm = WhiteSpaceNoWrap;
+ break;
+ case Html_th:
+ charFormat.setFontWeight(QFont::Bold);
+ blockFormat.setAlignment(Qt::AlignCenter);
+ break;
+ case Html_td:
+ blockFormat.setAlignment(Qt::AlignLeft);
+ break;
+ case Html_sub:
+ charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript);
+ break;
+ case Html_sup:
+ charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
+ break;
+ default: break;
+ }
+}
+
+#ifndef QT_NO_CSSPARSER
+void QTextHtmlParserNode::setListStyle(const QVector<QCss::Value> &cssValues)
+{
+ for (int i = 0; i < cssValues.count(); ++i) {
+ if (cssValues.at(i).type == QCss::Value::KnownIdentifier) {
+ switch (static_cast<QCss::KnownValue>(cssValues.at(i).variant.toInt())) {
+ case QCss::Value_Disc: hasOwnListStyle = true; listStyle = QTextListFormat::ListDisc; break;
+ case QCss::Value_Square: hasOwnListStyle = true; listStyle = QTextListFormat::ListSquare; break;
+ case QCss::Value_Circle: hasOwnListStyle = true; listStyle = QTextListFormat::ListCircle; break;
+ case QCss::Value_Decimal: hasOwnListStyle = true; listStyle = QTextListFormat::ListDecimal; break;
+ case QCss::Value_LowerAlpha: hasOwnListStyle = true; listStyle = QTextListFormat::ListLowerAlpha; break;
+ case QCss::Value_UpperAlpha: hasOwnListStyle = true; listStyle = QTextListFormat::ListUpperAlpha; break;
+ default: break;
+ }
+ }
+ }
+ // allow individual list items to override the style
+ if (id == Html_li && hasOwnListStyle)
+ blockFormat.setProperty(QTextFormat::ListStyle, listStyle);
+}
+
+void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration> &declarations, const QTextDocument *resourceProvider)
+{
+ QCss::ValueExtractor extractor(declarations);
+ extractor.extractBox(margin, padding);
+
+ for (int i = 0; i < declarations.count(); ++i) {
+ const QCss::Declaration &decl = declarations.at(i);
+ if (decl.d->values.isEmpty()) continue;
+
+ QCss::KnownValue identifier = QCss::UnknownValue;
+ if (decl.d->values.first().type == QCss::Value::KnownIdentifier)
+ identifier = static_cast<QCss::KnownValue>(decl.d->values.first().variant.toInt());
+
+ switch (decl.d->propertyId) {
+ case QCss::BorderColor: borderBrush = QBrush(decl.colorValue()); break;
+ case QCss::BorderStyles:
+ if (decl.styleValue() != QCss::BorderStyle_Unknown && decl.styleValue() != QCss::BorderStyle_Native)
+ borderStyle = static_cast<QTextFrameFormat::BorderStyle>(decl.styleValue() - 1);
+ break;
+ case QCss::BorderWidth:
+ tableBorder = extractor.lengthValue(decl);
+ break;
+ case QCss::Color: charFormat.setForeground(decl.colorValue()); break;
+ case QCss::Float:
+ cssFloat = QTextFrameFormat::InFlow;
+ switch (identifier) {
+ case QCss::Value_Left: cssFloat = QTextFrameFormat::FloatLeft; break;
+ case QCss::Value_Right: cssFloat = QTextFrameFormat::FloatRight; break;
+ default: break;
+ }
+ break;
+ case QCss::QtBlockIndent:
+ blockFormat.setIndent(decl.d->values.first().variant.toInt());
+ break;
+ case QCss::TextIndent: {
+ qreal indent = 0;
+ if (decl.realValue(&indent, "px"))
+ blockFormat.setTextIndent(indent);
+ break; }
+ case QCss::QtListIndent:
+ if (decl.intValue(&cssListIndent))
+ hasCssListIndent = true;
+ break;
+ case QCss::QtParagraphType:
+ if (decl.d->values.first().variant.toString().compare(QLatin1String("empty"), Qt::CaseInsensitive) == 0)
+ isEmptyParagraph = true;
+ break;
+ case QCss::QtTableType:
+ if (decl.d->values.first().variant.toString().compare(QLatin1String("frame"), Qt::CaseInsensitive) == 0)
+ isTextFrame = true;
+ else if (decl.d->values.first().variant.toString().compare(QLatin1String("root"), Qt::CaseInsensitive) == 0) {
+ isTextFrame = true;
+ isRootFrame = true;
+ }
+ break;
+ case QCss::QtUserState:
+ userState = decl.d->values.first().variant.toInt();
+ break;
+ case QCss::Whitespace:
+ switch (identifier) {
+ case QCss::Value_Normal: wsm = QTextHtmlParserNode::WhiteSpaceNormal; break;
+ case QCss::Value_Pre: wsm = QTextHtmlParserNode::WhiteSpacePre; break;
+ case QCss::Value_NoWrap: wsm = QTextHtmlParserNode::WhiteSpaceNoWrap; break;
+ case QCss::Value_PreWrap: wsm = QTextHtmlParserNode::WhiteSpacePreWrap; break;
+ default: break;
+ }
+ break;
+ case QCss::VerticalAlignment:
+ switch (identifier) {
+ case QCss::Value_Sub: charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript); break;
+ case QCss::Value_Super: charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript); break;
+ case QCss::Value_Middle: charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle); break;
+ case QCss::Value_Top: charFormat.setVerticalAlignment(QTextCharFormat::AlignTop); break;
+ case QCss::Value_Bottom: charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom); break;
+ default: charFormat.setVerticalAlignment(QTextCharFormat::AlignNormal); break;
+ }
+ break;
+ case QCss::PageBreakBefore:
+ switch (identifier) {
+ case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysBefore); break;
+ case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysBefore); break;
+ default: break;
+ }
+ break;
+ case QCss::PageBreakAfter:
+ switch (identifier) {
+ case QCss::Value_Always: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() | QTextFormat::PageBreak_AlwaysAfter); break;
+ case QCss::Value_Auto: blockFormat.setPageBreakPolicy(blockFormat.pageBreakPolicy() & ~QTextFormat::PageBreak_AlwaysAfter); break;
+ default: break;
+ }
+ break;
+ case QCss::TextUnderlineStyle:
+ switch (identifier) {
+ case QCss::Value_None: charFormat.setUnderlineStyle(QTextCharFormat::NoUnderline); break;
+ case QCss::Value_Solid: charFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); break;
+ case QCss::Value_Dashed: charFormat.setUnderlineStyle(QTextCharFormat::DashUnderline); break;
+ case QCss::Value_Dotted: charFormat.setUnderlineStyle(QTextCharFormat::DotLine); break;
+ case QCss::Value_DotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotLine); break;
+ case QCss::Value_DotDotDash: charFormat.setUnderlineStyle(QTextCharFormat::DashDotDotLine); break;
+ case QCss::Value_Wave: charFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline); break;
+ default: break;
+ }
+ break;
+ case QCss::ListStyleType:
+ case QCss::ListStyle:
+ setListStyle(decl.d->values);
+ break;
+ default: break;
+ }
+ }
+
+ QFont f;
+ int adjustment = -255;
+ extractor.extractFont(&f, &adjustment);
+ if (f.resolve() & QFont::SizeResolved) {
+ if (f.pointSize() > 0) {
+ charFormat.setFontPointSize(f.pointSize());
+ } else if (f.pixelSize() > 0) {
+ charFormat.setProperty(QTextFormat::FontPixelSize, f.pixelSize());
+ }
+ }
+ if (f.resolve() & QFont::StyleResolved)
+ charFormat.setFontItalic(f.style() != QFont::StyleNormal);
+
+ if (f.resolve() & QFont::WeightResolved)
+ charFormat.setFontWeight(f.weight());
+
+ if (f.resolve() & QFont::FamilyResolved)
+ charFormat.setFontFamily(f.family());
+
+ if (f.resolve() & QFont::UnderlineResolved)
+ charFormat.setUnderlineStyle(f.underline() ? QTextCharFormat::SingleUnderline : QTextCharFormat::NoUnderline);
+
+ if (f.resolve() & QFont::OverlineResolved)
+ charFormat.setFontOverline(f.overline());
+
+ if (f.resolve() & QFont::StrikeOutResolved)
+ charFormat.setFontStrikeOut(f.strikeOut());
+
+ if (f.resolve() & QFont::CapitalizationResolved)
+ charFormat.setFontCapitalization(f.capitalization());
+
+ if (adjustment >= -1)
+ charFormat.setProperty(QTextFormat::FontSizeAdjustment, adjustment);
+
+ {
+ Qt::Alignment ignoredAlignment;
+ QCss::Repeat ignoredRepeat;
+ QString bgImage;
+ QBrush bgBrush;
+ QCss::Origin ignoredOrigin, ignoredClip;
+ QCss::Attachment ignoredAttachment;
+ extractor.extractBackground(&bgBrush, &bgImage, &ignoredRepeat, &ignoredAlignment,
+ &ignoredOrigin, &ignoredAttachment, &ignoredClip);
+
+ if (!bgImage.isEmpty() && resourceProvider) {
+ applyBackgroundImage(bgImage, resourceProvider);
+ } else if (bgBrush.style() != Qt::NoBrush) {
+ charFormat.setBackground(bgBrush);
+ }
+ }
+}
+
+#endif // QT_NO_CSSPARSER
+
+void QTextHtmlParserNode::applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider)
+{
+ if (!url.isEmpty() && resourceProvider) {
+ QVariant val = resourceProvider->resource(QTextDocument::ImageResource, url);
+
+ if (qApp->thread() != QThread::currentThread()) {
+ // must use images in non-GUI threads
+ if (val.type() == QVariant::Image) {
+ QImage image = qvariant_cast<QImage>(val);
+ charFormat.setBackground(image);
+ } else if (val.type() == QVariant::ByteArray) {
+ QImage image;
+ if (image.loadFromData(val.toByteArray())) {
+ charFormat.setBackground(image);
+ }
+ }
+ } else {
+ if (val.type() == QVariant::Image || val.type() == QVariant::Pixmap) {
+ charFormat.setBackground(qvariant_cast<QPixmap>(val));
+ } else if (val.type() == QVariant::ByteArray) {
+ QPixmap pm;
+ if (pm.loadFromData(val.toByteArray())) {
+ charFormat.setBackground(pm);
+ }
+ }
+ }
+ }
+ if (!url.isEmpty())
+ charFormat.setProperty(QTextFormat::BackgroundImageUrl, url);
+}
+
+bool QTextHtmlParserNode::hasOnlyWhitespace() const
+{
+ for (int i = 0; i < text.count(); ++i)
+ if (!text.at(i).isSpace() || text.at(i) == QChar::LineSeparator)
+ return false;
+ return true;
+}
+
+static bool setIntAttribute(int *destination, const QString &value)
+{
+ bool ok = false;
+ int val = value.toInt(&ok);
+ if (ok)
+ *destination = val;
+
+ return ok;
+}
+
+static bool setFloatAttribute(qreal *destination, const QString &value)
+{
+ bool ok = false;
+ qreal val = value.toDouble(&ok);
+ if (ok)
+ *destination = val;
+
+ return ok;
+}
+
+static void setWidthAttribute(QTextLength *width, QString value)
+{
+ qreal realVal;
+ bool ok = false;
+ realVal = value.toDouble(&ok);
+ if (ok) {
+ *width = QTextLength(QTextLength::FixedLength, realVal);
+ } else {
+ value = value.trimmed();
+ if (!value.isEmpty() && value.at(value.length() - 1) == QLatin1Char('%')) {
+ value.chop(1);
+ realVal = value.toDouble(&ok);
+ if (ok)
+ *width = QTextLength(QTextLength::PercentageLength, realVal);
+ }
+ }
+}
+
+#ifndef QT_NO_CSSPARSER
+void QTextHtmlParserNode::parseStyleAttribute(const QString &value, const QTextDocument *resourceProvider)
+{
+ QString css = value;
+ css.prepend(QLatin1String("* {"));
+ css.append(QLatin1Char('}'));
+ QCss::Parser parser(css);
+ QCss::StyleSheet sheet;
+ parser.parse(&sheet, Qt::CaseInsensitive);
+ if (sheet.styleRules.count() != 1) return;
+ applyCssDeclarations(sheet.styleRules.at(0).declarations, resourceProvider);
+}
+#endif
+
+QStringList QTextHtmlParser::parseAttributes()
+{
+ QStringList attrs;
+
+ while (pos < len) {
+ eatSpace();
+ if (hasPrefix(QLatin1Char('>')) || hasPrefix(QLatin1Char('/')))
+ break;
+ QString key = parseWord().toLower();
+ QString value = QLatin1String("1");
+ if (key.size() == 0)
+ break;
+ eatSpace();
+ if (hasPrefix(QLatin1Char('='))){
+ pos++;
+ eatSpace();
+ value = parseWord();
+ }
+ if (value.size() == 0)
+ continue;
+ attrs << key << value;
+ }
+
+ return attrs;
+}
+
+void QTextHtmlParser::applyAttributes(const QStringList &attributes)
+{
+ // local state variable for qt3 textedit mode
+ bool seenQt3Richtext = false;
+ QString linkHref;
+ QString linkType;
+
+ if (attributes.count() % 2 == 1)
+ return;
+
+ QTextHtmlParserNode *node = &nodes.last();
+
+ for (int i = 0; i < attributes.count(); i += 2) {
+ QString key = attributes.at(i);
+ QString value = attributes.at(i + 1);
+
+ switch (node->id) {
+ case Html_font:
+ // the infamous font tag
+ if (key == QLatin1String("size") && value.size()) {
+ int n = value.toInt();
+ if (value.at(0) != QLatin1Char('+') && value.at(0) != QLatin1Char('-'))
+ n -= 3;
+ node->charFormat.setProperty(QTextFormat::FontSizeAdjustment, n);
+ } else if (key == QLatin1String("face")) {
+ node->charFormat.setFontFamily(value);
+ } else if (key == QLatin1String("color")) {
+ QColor c; c.setNamedColor(value);
+ if (!c.isValid())
+ qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
+ node->charFormat.setForeground(c);
+ }
+ break;
+ case Html_ol:
+ case Html_ul:
+ if (key == QLatin1String("type")) {
+ node->hasOwnListStyle = true;
+ if (value == QLatin1String("1")) {
+ node->listStyle = QTextListFormat::ListDecimal;
+ } else if (value == QLatin1String("a")) {
+ node->listStyle = QTextListFormat::ListLowerAlpha;
+ } else if (value == QLatin1String("A")) {
+ node->listStyle = QTextListFormat::ListUpperAlpha;
+ } else {
+ value = value.toLower();
+ if (value == QLatin1String("square"))
+ node->listStyle = QTextListFormat::ListSquare;
+ else if (value == QLatin1String("disc"))
+ node->listStyle = QTextListFormat::ListDisc;
+ else if (value == QLatin1String("circle"))
+ node->listStyle = QTextListFormat::ListCircle;
+ }
+ }
+ break;
+ case Html_a:
+ if (key == QLatin1String("href"))
+ node->charFormat.setAnchorHref(value);
+ else if (key == QLatin1String("name"))
+ node->charFormat.setAnchorName(value);
+ break;
+ case Html_img:
+ if (key == QLatin1String("src") || key == QLatin1String("source")) {
+ node->imageName = value;
+ } else if (key == QLatin1String("width")) {
+ node->imageWidth = -2; // register that there is a value for it.
+ setFloatAttribute(&node->imageWidth, value);
+ } else if (key == QLatin1String("height")) {
+ node->imageHeight = -2; // register that there is a value for it.
+ setFloatAttribute(&node->imageHeight, value);
+ }
+ break;
+ case Html_tr:
+ case Html_body:
+ if (key == QLatin1String("bgcolor")) {
+ QColor c; c.setNamedColor(value);
+ if (!c.isValid())
+ qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
+ node->charFormat.setBackground(c);
+ } else if (key == QLatin1String("background")) {
+ node->applyBackgroundImage(value, resourceProvider);
+ }
+ break;
+ case Html_th:
+ case Html_td:
+ if (key == QLatin1String("width")) {
+ setWidthAttribute(&node->width, value);
+ } else if (key == QLatin1String("bgcolor")) {
+ QColor c; c.setNamedColor(value);
+ if (!c.isValid())
+ qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
+ node->charFormat.setBackground(c);
+ } else if (key == QLatin1String("background")) {
+ node->applyBackgroundImage(value, resourceProvider);
+ } else if (key == QLatin1String("rowspan")) {
+ if (setIntAttribute(&node->tableCellRowSpan, value))
+ node->tableCellRowSpan = qMax(1, node->tableCellRowSpan);
+ } else if (key == QLatin1String("colspan")) {
+ if (setIntAttribute(&node->tableCellColSpan, value))
+ node->tableCellColSpan = qMax(1, node->tableCellColSpan);
+ }
+ break;
+ case Html_table:
+ if (key == QLatin1String("border")) {
+ setFloatAttribute(&node->tableBorder, value);
+ } else if (key == QLatin1String("bgcolor")) {
+ QColor c; c.setNamedColor(value);
+ if (!c.isValid())
+ qWarning("QTextHtmlParser::applyAttributes: Unknown color name '%s'",value.toLatin1().constData());
+ node->charFormat.setBackground(c);
+ } else if (key == QLatin1String("background")) {
+ node->applyBackgroundImage(value, resourceProvider);
+ } else if (key == QLatin1String("cellspacing")) {
+ setFloatAttribute(&node->tableCellSpacing, value);
+ } else if (key == QLatin1String("cellpadding")) {
+ setFloatAttribute(&node->tableCellPadding, value);
+ } else if (key == QLatin1String("width")) {
+ setWidthAttribute(&node->width, value);
+ } else if (key == QLatin1String("height")) {
+ setWidthAttribute(&node->height, value);
+ }
+ break;
+ case Html_meta:
+ if (key == QLatin1String("name")
+ && value == QLatin1String("qrichtext")) {
+ seenQt3Richtext = true;
+ }
+
+ if (key == QLatin1String("content")
+ && value == QLatin1String("1")
+ && seenQt3Richtext) {
+
+ textEditMode = true;
+ }
+ break;
+ case Html_hr:
+ if (key == QLatin1String("width"))
+ setWidthAttribute(&node->width, value);
+ break;
+ case Html_link:
+ if (key == QLatin1String("href"))
+ linkHref = value;
+ else if (key == QLatin1String("type"))
+ linkType = value;
+ break;
+ default:
+ break;
+ }
+
+ if (key == QLatin1String("style")) {
+#ifndef QT_NO_CSSPARSER
+ node->parseStyleAttribute(value, resourceProvider);
+#endif
+ } else if (key == QLatin1String("align")) {
+ value = value.toLower();
+ bool alignmentSet = true;
+
+ if (value == QLatin1String("left"))
+ node->blockFormat.setAlignment(Qt::AlignLeft|Qt::AlignAbsolute);
+ else if (value == QLatin1String("right"))
+ node->blockFormat.setAlignment(Qt::AlignRight|Qt::AlignAbsolute);
+ else if (value == QLatin1String("center"))
+ node->blockFormat.setAlignment(Qt::AlignHCenter);
+ else if (value == QLatin1String("justify"))
+ node->blockFormat.setAlignment(Qt::AlignJustify);
+ else
+ alignmentSet = false;
+
+ if (node->id == Html_img) {
+ // HTML4 compat
+ if (alignmentSet) {
+ if (node->blockFormat.alignment() & Qt::AlignLeft)
+ node->cssFloat = QTextFrameFormat::FloatLeft;
+ else if (node->blockFormat.alignment() & Qt::AlignRight)
+ node->cssFloat = QTextFrameFormat::FloatRight;
+ } else if (value == QLatin1String("middle")) {
+ node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
+ } else if (value == QLatin1String("top")) {
+ node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
+ }
+ }
+ } else if (key == QLatin1String("valign")) {
+ value = value.toLower();
+ if (value == QLatin1String("top"))
+ node->charFormat.setVerticalAlignment(QTextCharFormat::AlignTop);
+ else if (value == QLatin1String("middle"))
+ node->charFormat.setVerticalAlignment(QTextCharFormat::AlignMiddle);
+ else if (value == QLatin1String("bottom"))
+ node->charFormat.setVerticalAlignment(QTextCharFormat::AlignBottom);
+ } else if (key == QLatin1String("dir")) {
+ value = value.toLower();
+ if (value == QLatin1String("ltr"))
+ node->blockFormat.setLayoutDirection(Qt::LeftToRight);
+ else if (value == QLatin1String("rtl"))
+ node->blockFormat.setLayoutDirection(Qt::RightToLeft);
+ } else if (key == QLatin1String("title")) {
+ node->charFormat.setToolTip(value);
+ } else if (key == QLatin1String("id")) {
+ node->charFormat.setAnchor(true);
+ node->charFormat.setAnchorName(value);
+ }
+ }
+
+#ifndef QT_NO_CSSPARSER
+ if (resourceProvider && !linkHref.isEmpty() && linkType == QLatin1String("text/css"))
+ importStyleSheet(linkHref);
+#endif
+}
+
+#ifndef QT_NO_CSSPARSER
+class QTextHtmlStyleSelector : public QCss::StyleSelector
+{
+public:
+ inline QTextHtmlStyleSelector(const QTextHtmlParser *parser)
+ : parser(parser) { nameCaseSensitivity = Qt::CaseInsensitive; }
+
+ virtual QStringList nodeNames(NodePtr node) const;
+ virtual QString attribute(NodePtr node, const QString &name) const;
+ virtual bool hasAttributes(NodePtr node) const;
+ virtual bool isNullNode(NodePtr node) const;
+ virtual NodePtr parentNode(NodePtr node) const;
+ virtual NodePtr previousSiblingNode(NodePtr node) const;
+ virtual NodePtr duplicateNode(NodePtr node) const;
+ virtual void freeNode(NodePtr node) const;
+
+private:
+ const QTextHtmlParser *parser;
+};
+
+QStringList QTextHtmlStyleSelector::nodeNames(NodePtr node) const
+{
+ return QStringList(parser->at(node.id).tag.toLower());
+}
+
+#endif // QT_NO_CSSPARSER
+
+static inline int findAttribute(const QStringList &attributes, const QString &name)
+{
+ int idx = -1;
+ do {
+ idx = attributes.indexOf(name, idx + 1);
+ } while (idx != -1 && (idx % 2 == 1));
+ return idx;
+}
+
+#ifndef QT_NO_CSSPARSER
+
+QString QTextHtmlStyleSelector::attribute(NodePtr node, const QString &name) const
+{
+ const QStringList &attributes = parser->at(node.id).attributes;
+ const int idx = findAttribute(attributes, name);
+ if (idx == -1)
+ return QString();
+ return attributes.at(idx + 1);
+}
+
+bool QTextHtmlStyleSelector::hasAttributes(NodePtr node) const
+{
+ const QStringList &attributes = parser->at(node.id).attributes;
+ return !attributes.isEmpty();
+}
+
+bool QTextHtmlStyleSelector::isNullNode(NodePtr node) const
+{
+ return node.id == 0;
+}
+
+QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::parentNode(NodePtr node) const
+{
+ NodePtr parent;
+ parent.id = 0;
+ if (node.id) {
+ parent.id = parser->at(node.id).parent;
+ }
+ return parent;
+}
+
+QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::duplicateNode(NodePtr node) const
+{
+ return node;
+}
+
+QCss::StyleSelector::NodePtr QTextHtmlStyleSelector::previousSiblingNode(NodePtr node) const
+{
+ NodePtr sibling;
+ sibling.id = 0;
+ if (!node.id)
+ return sibling;
+ int parent = parser->at(node.id).parent;
+ if (!parent)
+ return sibling;
+ const int childIdx = parser->at(parent).children.indexOf(node.id);
+ if (childIdx <= 0)
+ return sibling;
+ sibling.id = parser->at(parent).children.at(childIdx - 1);
+ return sibling;
+}
+
+void QTextHtmlStyleSelector::freeNode(NodePtr) const
+{
+}
+
+void QTextHtmlParser::resolveStyleSheetImports(const QCss::StyleSheet &sheet)
+{
+ for (int i = 0; i < sheet.importRules.count(); ++i) {
+ const QCss::ImportRule &rule = sheet.importRules.at(i);
+ if (rule.media.isEmpty()
+ || rule.media.contains(QLatin1String("screen"), Qt::CaseInsensitive))
+ importStyleSheet(rule.href);
+ }
+}
+
+void QTextHtmlParser::importStyleSheet(const QString &href)
+{
+ if (!resourceProvider)
+ return;
+ for (int i = 0; i < externalStyleSheets.count(); ++i)
+ if (externalStyleSheets.at(i).url == href)
+ return;
+
+ QVariant res = resourceProvider->resource(QTextDocument::StyleSheetResource, href);
+ QString css;
+ if (res.type() == QVariant::String) {
+ css = res.toString();
+ } else if (res.type() == QVariant::ByteArray) {
+ // #### detect @charset
+ css = QString::fromUtf8(res.toByteArray());
+ }
+ if (!css.isEmpty()) {
+ QCss::Parser parser(css);
+ QCss::StyleSheet sheet;
+ parser.parse(&sheet, Qt::CaseInsensitive);
+ externalStyleSheets.append(ExternalStyleSheet(href, sheet));
+ resolveStyleSheetImports(sheet);
+ }
+}
+
+QVector<QCss::Declaration> QTextHtmlParser::declarationsForNode(int node) const
+{
+ QVector<QCss::Declaration> decls;
+
+ QTextHtmlStyleSelector selector(this);
+
+ int idx = 0;
+ selector.styleSheets.resize((resourceProvider ? 1 : 0)
+ + externalStyleSheets.count()
+ + inlineStyleSheets.count());
+ if (resourceProvider)
+ selector.styleSheets[idx++] = resourceProvider->docHandle()->parsedDefaultStyleSheet;
+
+ for (int i = 0; i < externalStyleSheets.count(); ++i, ++idx)
+ selector.styleSheets[idx] = externalStyleSheets.at(i).sheet;
+
+ for (int i = 0; i < inlineStyleSheets.count(); ++i, ++idx)
+ selector.styleSheets[idx] = inlineStyleSheets.at(i);
+
+ selector.medium = QLatin1String("screen");
+
+ QCss::StyleSelector::NodePtr n;
+ n.id = node;
+
+ const char *extraPseudo = 0;
+ if (nodes.at(node).id == Html_a && nodes.at(node).hasHref)
+ extraPseudo = "link";
+ decls = selector.declarationsForNode(n, extraPseudo);
+
+ return decls;
+}
+
+bool QTextHtmlParser::nodeIsChildOf(int i, QTextHTMLElements id) const
+{
+ while (i) {
+ if (at(i).id == id)
+ return true;
+ i = at(i).parent;
+ }
+ return false;
+}
+
+QT_END_NAMESPACE
+#endif // QT_NO_CSSPARSER
+
+#endif // QT_NO_TEXTHTMLPARSER
diff --git a/src/gui/text/qtexthtmlparser_p.h b/src/gui/text/qtexthtmlparser_p.h
new file mode 100644
index 0000000000..a27b2f2ca1
--- /dev/null
+++ b/src/gui/text/qtexthtmlparser_p.h
@@ -0,0 +1,342 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTHTMLPARSER_P_H
+#define QTEXTHTMLPARSER_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/qvector.h"
+#include "QtGui/qbrush.h"
+#include "QtGui/qcolor.h"
+#include "QtGui/qfont.h"
+#include "QtGui/qtextdocument.h"
+#include "QtGui/qtextcursor.h"
+#include "private/qtextformat_p.h"
+#include "private/qtextdocument_p.h"
+#include "private/qcssparser_p.h"
+
+#ifndef QT_NO_TEXTHTMLPARSER
+
+QT_BEGIN_NAMESPACE
+
+enum QTextHTMLElements {
+ Html_unknown = -1,
+ Html_qt = 0,
+ Html_body,
+
+ Html_a,
+ Html_em,
+ Html_i,
+ Html_big,
+ Html_small,
+ Html_strong,
+ Html_b,
+ Html_cite,
+ Html_address,
+ Html_var,
+ Html_dfn,
+
+ Html_h1,
+ Html_h2,
+ Html_h3,
+ Html_h4,
+ Html_h5,
+ Html_h6,
+ Html_p,
+ Html_center,
+
+ Html_font,
+
+ Html_ul,
+ Html_ol,
+ Html_li,
+
+ Html_code,
+ Html_tt,
+ Html_kbd,
+ Html_samp,
+
+ Html_img,
+ Html_br,
+ Html_hr,
+
+ Html_sub,
+ Html_sup,
+
+ Html_pre,
+ Html_blockquote,
+ Html_head,
+ Html_div,
+ Html_span,
+ Html_dl,
+ Html_dt,
+ Html_dd,
+ Html_u,
+ Html_s,
+ Html_nobr,
+
+ // tables
+ Html_table,
+ Html_tr,
+ Html_td,
+ Html_th,
+ Html_thead,
+ Html_tbody,
+ Html_tfoot,
+ Html_caption,
+
+ // misc...
+ Html_html,
+ Html_style,
+ Html_title,
+ Html_meta,
+ Html_link,
+ Html_script,
+
+ Html_NumElements
+};
+
+struct QTextHtmlElement
+{
+ const char *name;
+ QTextHTMLElements id;
+ enum DisplayMode { DisplayBlock, DisplayInline, DisplayTable, DisplayNone } displayMode;
+};
+
+class QTextHtmlParser;
+
+struct QTextHtmlParserNode {
+ enum WhiteSpaceMode {
+ WhiteSpaceNormal,
+ WhiteSpacePre,
+ WhiteSpaceNoWrap,
+ WhiteSpacePreWrap,
+ WhiteSpaceModeUndefined = -1
+ };
+
+ QTextHtmlParserNode();
+ QString tag;
+ QString text;
+ QStringList attributes;
+ int parent;
+ QVector<int> children;
+ QTextHTMLElements id;
+ QTextCharFormat charFormat;
+ QTextBlockFormat blockFormat;
+ uint cssFloat : 2;
+ uint hasOwnListStyle : 1;
+ uint hasCssListIndent : 1;
+ uint isEmptyParagraph : 1;
+ uint isTextFrame : 1;
+ uint isRootFrame : 1;
+ uint displayMode : 3; // QTextHtmlElement::DisplayMode
+ uint hasHref : 1;
+ QTextListFormat::Style listStyle;
+ QString imageName;
+ qreal imageWidth;
+ qreal imageHeight;
+ QTextLength width;
+ QTextLength height;
+ qreal tableBorder;
+ int tableCellRowSpan;
+ int tableCellColSpan;
+ qreal tableCellSpacing;
+ qreal tableCellPadding;
+ QBrush borderBrush;
+ QTextFrameFormat::BorderStyle borderStyle;
+ int userState;
+
+ int cssListIndent;
+
+ WhiteSpaceMode wsm;
+
+ inline bool isListStart() const
+ { return id == Html_ol || id == Html_ul; }
+ inline bool isTableCell() const
+ { return id == Html_td || id == Html_th; }
+ inline bool isBlock() const
+ { return displayMode == QTextHtmlElement::DisplayBlock; }
+
+ inline bool isNotSelfNesting() const
+ { return id == Html_p || id == Html_li; }
+
+ inline bool allowedInContext(int parentId) const
+ {
+ switch (id) {
+ case Html_dd:
+ case Html_dt: return (parentId == Html_dl);
+ case Html_tr: return (parentId == Html_table
+ || parentId == Html_thead
+ || parentId == Html_tbody
+ || parentId == Html_tfoot
+ );
+ case Html_th:
+ case Html_td: return (parentId == Html_tr);
+ case Html_thead:
+ case Html_tbody:
+ case Html_tfoot: return (parentId == Html_table);
+ case Html_caption: return (parentId == Html_table);
+ case Html_body: return parentId != Html_head;
+ default: break;
+ }
+ return true;
+ }
+
+ inline bool mayNotHaveChildren() const
+ { return id == Html_img || id == Html_hr || id == Html_br || id == Html_meta; }
+
+ void initializeProperties(const QTextHtmlParserNode *parent, const QTextHtmlParser *parser);
+
+ inline int uncollapsedMargin(int mar) const { return margin[mar]; }
+
+ bool isNestedList(const QTextHtmlParser *parser) const;
+
+ void parseStyleAttribute(const QString &value, const QTextDocument *resourceProvider);
+
+#ifndef QT_NO_CSSPARSER
+ void applyCssDeclarations(const QVector<QCss::Declaration> &declarations, const QTextDocument *resourceProvider);
+
+ void setListStyle(const QVector<QCss::Value> &cssValues);
+#endif
+
+ void applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider);
+
+ bool hasOnlyWhitespace() const;
+
+ int margin[4];
+ int padding[4];
+
+ friend class QTextHtmlParser;
+};
+Q_DECLARE_TYPEINFO(QTextHtmlParserNode, Q_MOVABLE_TYPE);
+
+
+class QTextHtmlParser
+{
+public:
+ enum Margin {
+ MarginTop,
+ MarginRight,
+ MarginBottom,
+ MarginLeft
+ };
+
+ inline const QTextHtmlParserNode &at(int i) const { return nodes.at(i); }
+ inline QTextHtmlParserNode &operator[](int i) { return nodes[i]; }
+ inline int count() const { return nodes.count(); }
+ inline int last() const { return nodes.count()-1; }
+ int depth(int i) const;
+ int topMargin(int i) const;
+ int bottomMargin(int i) const;
+ inline int leftMargin(int i) const { return margin(i, MarginLeft); }
+ inline int rightMargin(int i) const { return margin(i, MarginRight); }
+
+ inline int topPadding(int i) const { return at(i).padding[MarginTop]; }
+ inline int bottomPadding(int i) const { return at(i).padding[MarginBottom]; }
+ inline int leftPadding(int i) const { return at(i).padding[MarginLeft]; }
+ inline int rightPadding(int i) const { return at(i).padding[MarginRight]; }
+
+ void dumpHtml();
+
+ void parse(const QString &text, const QTextDocument *resourceProvider);
+
+ static int lookupElement(const QString &element);
+protected:
+ QTextHtmlParserNode *newNode(int parent);
+ QVector<QTextHtmlParserNode> nodes;
+ QString txt;
+ int pos, len;
+
+ bool textEditMode;
+
+ void parse();
+ void parseTag();
+ void parseCloseTag();
+ void parseExclamationTag();
+ QString parseEntity();
+ QString parseWord();
+ QTextHtmlParserNode *resolveParent();
+ void resolveNode();
+ QStringList parseAttributes();
+ void applyAttributes(const QStringList &attributes);
+ void eatSpace();
+ inline bool hasPrefix(QChar c, int lookahead = 0) const
+ {return pos + lookahead < len && txt.at(pos) == c; }
+ int margin(int i, int mar) const;
+
+ bool nodeIsChildOf(int i, QTextHTMLElements id) const;
+
+
+#ifndef QT_NO_CSSPARSER
+ QVector<QCss::Declaration> declarationsForNode(int node) const;
+ void resolveStyleSheetImports(const QCss::StyleSheet &sheet);
+ void importStyleSheet(const QString &href);
+
+ struct ExternalStyleSheet
+ {
+ inline ExternalStyleSheet() {}
+ inline ExternalStyleSheet(const QString &_url, const QCss::StyleSheet &_sheet)
+ : url(_url), sheet(_sheet) {}
+ QString url;
+ QCss::StyleSheet sheet;
+ };
+ QVector<ExternalStyleSheet> externalStyleSheets;
+ QVector<QCss::StyleSheet> inlineStyleSheets;
+#endif
+
+ const QTextDocument *resourceProvider;
+};
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_TEXTHTMLPARSER
+
+#endif // QTEXTHTMLPARSER_P_H
diff --git a/src/gui/text/qtextimagehandler.cpp b/src/gui/text/qtextimagehandler.cpp
new file mode 100644
index 0000000000..c04f2258ef
--- /dev/null
+++ b/src/gui/text/qtextimagehandler.cpp
@@ -0,0 +1,234 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include "qtextimagehandler_p.h"
+
+#include <qapplication.h>
+#include <qtextformat.h>
+#include <qpainter.h>
+#include <qdebug.h>
+#include <private/qtextengine_p.h>
+#include <qpalette.h>
+#include <qtextbrowser.h>
+#include <qthread.h>
+
+QT_BEGIN_NAMESPACE
+
+// set by the mime source factory in Qt3Compat
+QTextImageHandler::ExternalImageLoaderFunction QTextImageHandler::externalLoader = 0;
+
+static QPixmap getPixmap(QTextDocument *doc, const QTextImageFormat &format)
+{
+ QPixmap pm;
+
+ QString name = format.name();
+ if (name.startsWith(QLatin1String(":/"))) // auto-detect resources
+ name.prepend(QLatin1String("qrc"));
+ QUrl url = QUrl::fromEncoded(name.toUtf8());
+ const QVariant data = doc->resource(QTextDocument::ImageResource, url);
+ if (data.type() == QVariant::Pixmap || data.type() == QVariant::Image) {
+ pm = qvariant_cast<QPixmap>(data);
+ } else if (data.type() == QVariant::ByteArray) {
+ pm.loadFromData(data.toByteArray());
+ }
+
+ if (pm.isNull()) {
+ QString context;
+#ifndef QT_NO_TEXTBROWSER
+ QTextBrowser *browser = qobject_cast<QTextBrowser *>(doc->parent());
+ if (browser)
+ context = browser->source().toString();
+#endif
+ QImage img;
+ if (QTextImageHandler::externalLoader)
+ img = QTextImageHandler::externalLoader(name, context);
+
+ if (img.isNull()) { // try direct loading
+ name = format.name(); // remove qrc:/ prefix again
+ if (name.isEmpty() || !img.load(name))
+ return QPixmap(QLatin1String(":/trolltech/styles/commonstyle/images/file-16.png"));
+ }
+ pm = QPixmap::fromImage(img);
+ doc->addResource(QTextDocument::ImageResource, url, pm);
+ }
+
+ return pm;
+}
+
+static QSize getPixmapSize(QTextDocument *doc, const QTextImageFormat &format)
+{
+ QPixmap pm;
+
+ const bool hasWidth = format.hasProperty(QTextFormat::ImageWidth);
+ const int width = qRound(format.width());
+ const bool hasHeight = format.hasProperty(QTextFormat::ImageHeight);
+ const int height = qRound(format.height());
+
+ QSize size(width, height);
+ if (!hasWidth || !hasHeight) {
+ pm = getPixmap(doc, format);
+ if (!hasWidth) {
+ if (!hasHeight)
+ size.setWidth(pm.width());
+ else
+ size.setWidth(qRound(height * (pm.width() / (qreal) pm.height())));
+ }
+ if (!hasHeight) {
+ if (!hasWidth)
+ size.setHeight(pm.height());
+ else
+ size.setHeight(qRound(width * (pm.height() / (qreal) pm.width())));
+ }
+ }
+
+ qreal scale = 1.0;
+ QPaintDevice *pdev = doc->documentLayout()->paintDevice();
+ if (pdev) {
+ extern int qt_defaultDpi();
+ if (pm.isNull())
+ pm = getPixmap(doc, format);
+ if (!pm.isNull())
+ scale = qreal(pdev->logicalDpiY()) / qreal(qt_defaultDpi());
+ }
+ size *= scale;
+
+ return size;
+}
+
+static QImage getImage(QTextDocument *doc, const QTextImageFormat &format)
+{
+ QImage image;
+
+ QString name = format.name();
+ if (name.startsWith(QLatin1String(":/"))) // auto-detect resources
+ name.prepend(QLatin1String("qrc"));
+ QUrl url = QUrl::fromEncoded(name.toUtf8());
+ const QVariant data = doc->resource(QTextDocument::ImageResource, url);
+ if (data.type() == QVariant::Image) {
+ image = qvariant_cast<QImage>(data);
+ } else if (data.type() == QVariant::ByteArray) {
+ image.loadFromData(data.toByteArray());
+ }
+
+ if (image.isNull()) {
+ QString context;
+#ifndef QT_NO_TEXTBROWSER
+ QTextBrowser *browser = qobject_cast<QTextBrowser *>(doc->parent());
+ if (browser)
+ context = browser->source().toString();
+#endif
+ if (QTextImageHandler::externalLoader)
+ image = QTextImageHandler::externalLoader(name, context);
+
+ if (image.isNull()) { // try direct loading
+ name = format.name(); // remove qrc:/ prefix again
+ if (name.isEmpty() || !image.load(name))
+ return QImage(QLatin1String(":/trolltech/styles/commonstyle/images/file-16.png"));
+ }
+ doc->addResource(QTextDocument::ImageResource, url, image);
+ }
+
+ return image;
+}
+
+static QSize getImageSize(QTextDocument *doc, const QTextImageFormat &format)
+{
+ QImage image;
+
+ const bool hasWidth = format.hasProperty(QTextFormat::ImageWidth);
+ const int width = qRound(format.width());
+ const bool hasHeight = format.hasProperty(QTextFormat::ImageHeight);
+ const int height = qRound(format.height());
+
+ QSize size(width, height);
+ if (!hasWidth || !hasHeight) {
+ image = getImage(doc, format);
+ if (!hasWidth)
+ size.setWidth(image.width());
+ if (!hasHeight)
+ size.setHeight(image.height());
+ }
+
+ qreal scale = 1.0;
+ QPaintDevice *pdev = doc->documentLayout()->paintDevice();
+ if (pdev) {
+ extern int qt_defaultDpi();
+ if (image.isNull())
+ image = getImage(doc, format);
+ if (!image.isNull())
+ scale = qreal(pdev->logicalDpiY()) / qreal(qt_defaultDpi());
+ }
+ size *= scale;
+
+ return size;
+}
+
+QTextImageHandler::QTextImageHandler(QObject *parent)
+ : QObject(parent)
+{
+}
+
+QSizeF QTextImageHandler::intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format)
+{
+ Q_UNUSED(posInDocument)
+ const QTextImageFormat imageFormat = format.toImageFormat();
+
+ if (qApp->thread() != QThread::currentThread())
+ return getImageSize(doc, imageFormat);
+ return getPixmapSize(doc, imageFormat);
+}
+
+void QTextImageHandler::drawObject(QPainter *p, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format)
+{
+ Q_UNUSED(posInDocument)
+ const QTextImageFormat imageFormat = format.toImageFormat();
+
+ if (qApp->thread() != QThread::currentThread()) {
+ const QImage image = getImage(doc, imageFormat);
+ p->drawImage(rect, image, image.rect());
+ } else {
+ const QPixmap pixmap = getPixmap(doc, imageFormat);
+ p->drawPixmap(rect, pixmap, pixmap.rect());
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/text/qtextimagehandler_p.h b/src/gui/text/qtextimagehandler_p.h
new file mode 100644
index 0000000000..f5426b549d
--- /dev/null
+++ b/src/gui/text/qtextimagehandler_p.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTIMAGEHANDLER_P_H
+#define QTEXTIMAGEHANDLER_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/qobject.h"
+#include "QtGui/qabstracttextdocumentlayout.h"
+
+QT_BEGIN_NAMESPACE
+
+class QTextImageFormat;
+
+class Q_GUI_EXPORT QTextImageHandler : public QObject,
+ public QTextObjectInterface
+{
+ Q_OBJECT
+ Q_INTERFACES(QTextObjectInterface)
+public:
+ explicit QTextImageHandler(QObject *parent = 0);
+
+ virtual QSizeF intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format);
+ virtual void drawObject(QPainter *p, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format);
+
+ typedef QImage (*ExternalImageLoaderFunction)(const QString &name, const QString &context);
+ static ExternalImageLoaderFunction externalLoader;
+};
+
+QT_END_NAMESPACE
+
+#endif // QTEXTIMAGEHANDLER_P_H
diff --git a/src/gui/text/qtextlayout.cpp b/src/gui/text/qtextlayout.cpp
new file mode 100644
index 0000000000..434d1cae87
--- /dev/null
+++ b/src/gui/text/qtextlayout.cpp
@@ -0,0 +1,2453 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtextlayout.h"
+#include "qtextengine_p.h"
+
+#include <qfont.h>
+#include <qapplication.h>
+#include <qpainter.h>
+#include <qvarlengtharray.h>
+#include <qtextformat.h>
+#include <qabstracttextdocumentlayout.h>
+#include "qtextdocument_p.h"
+#include "qtextformat_p.h"
+#include "qstyleoption.h"
+#include "qpainterpath.h"
+#include <limits.h>
+
+#include <qdebug.h>
+
+#include "qfontengine_p.h"
+
+QT_BEGIN_NAMESPACE
+
+#define ObjectSelectionBrush (QTextFormat::ForegroundBrush + 1)
+
+static inline QFixed leadingSpaceWidth(QTextEngine *eng, const QScriptLine &line)
+{
+ if (!line.hasTrailingSpaces
+ || (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
+ || !(eng->option.alignment() & Qt::AlignRight)
+ || (eng->option.textDirection() != Qt::RightToLeft))
+ return QFixed();
+
+ int pos = line.length;
+ const HB_CharAttributes *attributes = eng->attributes();
+ while (pos > 0 && attributes[line.from + pos - 1].whiteSpace)
+ --pos;
+ return eng->width(line.from + pos, line.length - pos);
+}
+
+static QFixed alignLine(QTextEngine *eng, const QScriptLine &line)
+{
+ QFixed x = 0;
+ eng->justify(line);
+ // if width is QFIXED_MAX that means we used setNumColumns() and that implicitly makes this line left aligned.
+ if (!line.justified && line.width != QFIXED_MAX) {
+ int align = eng->option.alignment();
+ if (align & Qt::AlignJustify && eng->option.textDirection() == Qt::RightToLeft)
+ align = Qt::AlignRight;
+ if (align & Qt::AlignRight)
+ x = line.width - (line.textWidth + leadingSpaceWidth(eng, line));
+ else if (align & Qt::AlignHCenter)
+ x = (line.width - line.textWidth)/2;
+ }
+ return x;
+}
+
+/*!
+ \class QTextLayout::FormatRange
+ \reentrant
+
+ \brief The QTextLayout::FormatRange structure is used to apply extra formatting information
+ for a specified area in the text layout's content.
+
+ \sa QTextLayout::setAdditionalFormats(), QTextLayout::draw()
+*/
+
+/*!
+ \variable QTextLayout::FormatRange::start
+ Specifies the beginning of the format range within the text layout's text.
+*/
+
+/*!
+ \variable QTextLayout::FormatRange::length
+ Specifies the numer of characters the format range spans.
+*/
+
+/*!
+ \variable QTextLayout::FormatRange::format
+ Specifies the format to apply.
+*/
+
+/*!
+ \class QTextInlineObject
+ \reentrant
+
+ \brief The QTextInlineObject class represents an inline object in
+ a QTextLayout.
+
+ \ingroup text
+
+ This class is only used if the text layout is used to lay out
+ parts of a QTextDocument.
+
+ The inline object has various attributes that can be set, for
+ example using, setWidth(), setAscent(), and setDescent(). The
+ rectangle it occupies is given by rect(), and its direction by
+ isRightToLeft(). Its position in the text layout is given by at(),
+ and its format is given by format().
+*/
+
+/*!
+ \fn QTextInlineObject::QTextInlineObject(int i, QTextEngine *e)
+
+ Creates a new inline object for the item at position \a i in the
+ text engine \a e.
+*/
+
+/*!
+ \fn QTextInlineObject::QTextInlineObject()
+
+ \internal
+*/
+
+/*!
+ \fn bool QTextInlineObject::isValid() const
+
+ Returns true if this inline object is valid; otherwise returns
+ false.
+*/
+
+/*!
+ Returns the inline object's rectangle.
+
+ \sa ascent() descent() width()
+*/
+QRectF QTextInlineObject::rect() const
+{
+ QScriptItem& si = eng->layoutData->items[itm];
+ return QRectF(0, -si.ascent.toReal(), si.width.toReal(), si.height().toReal());
+}
+
+/*!
+ Returns the inline object's width.
+
+ \sa ascent() descent() rect()
+*/
+qreal QTextInlineObject::width() const
+{
+ return eng->layoutData->items[itm].width.toReal();
+}
+
+/*!
+ Returns the inline object's ascent.
+
+ \sa descent() width() rect()
+*/
+qreal QTextInlineObject::ascent() const
+{
+ return eng->layoutData->items[itm].ascent.toReal();
+}
+
+/*!
+ Returns the inline object's descent.
+
+ \sa ascent() width() rect()
+*/
+qreal QTextInlineObject::descent() const
+{
+ return eng->layoutData->items[itm].descent.toReal();
+}
+
+/*!
+ Returns the inline object's total height. This is equal to
+ ascent() + descent() + 1.
+
+ \sa ascent() descent() width() rect()
+*/
+qreal QTextInlineObject::height() const
+{
+ return eng->layoutData->items[itm].height().toReal();
+}
+
+
+/*!
+ Sets the inline object's width to \a w.
+
+ \sa width() ascent() descent() rect()
+*/
+void QTextInlineObject::setWidth(qreal w)
+{
+ eng->layoutData->items[itm].width = QFixed::fromReal(w);
+}
+
+/*!
+ Sets the inline object's ascent to \a a.
+
+ \sa ascent() setDescent() width() rect()
+*/
+void QTextInlineObject::setAscent(qreal a)
+{
+ eng->layoutData->items[itm].ascent = QFixed::fromReal(a);
+}
+
+/*!
+ Sets the inline object's decent to \a d.
+
+ \sa descent() setAscent() width() rect()
+*/
+void QTextInlineObject::setDescent(qreal d)
+{
+ eng->layoutData->items[itm].descent = QFixed::fromReal(d);
+}
+
+/*!
+ The position of the inline object within the text layout.
+*/
+int QTextInlineObject::textPosition() const
+{
+ return eng->layoutData->items[itm].position;
+}
+
+/*!
+ Returns an integer describing the format of the inline object
+ within the text layout.
+*/
+int QTextInlineObject::formatIndex() const
+{
+ return eng->formatIndex(&eng->layoutData->items[itm]);
+}
+
+/*!
+ Returns format of the inline object within the text layout.
+*/
+QTextFormat QTextInlineObject::format() const
+{
+ if (!eng->block.docHandle())
+ return QTextFormat();
+ return eng->formats()->format(eng->formatIndex(&eng->layoutData->items[itm]));
+}
+
+/*!
+ Returns if the object should be laid out right-to-left or left-to-right.
+*/
+Qt::LayoutDirection QTextInlineObject::textDirection() const
+{
+ return (eng->layoutData->items[itm].analysis.bidiLevel % 2 ? Qt::RightToLeft : Qt::LeftToRight);
+}
+
+/*!
+ \class QTextLayout
+ \reentrant
+
+ \brief The QTextLayout class is used to lay out and paint a single
+ paragraph of text.
+
+ \ingroup text
+
+ It offers most features expected from a modern text layout
+ engine, including Unicode compliant rendering, line breaking and
+ handling of cursor positioning. It can also produce and render
+ device independent layout, something that is important for WYSIWYG
+ applications.
+
+ The class has a rather low level API and unless you intend to
+ implement your own text rendering for some specialized widget, you
+ probably won't need to use it directly.
+
+ QTextLayout can currently deal with plain text and rich text
+ paragraphs that are part of a QTextDocument.
+
+ QTextLayout can be used to create a sequence of QTextLine's with
+ given widths and can position them independently on the screen.
+ Once the layout is done, these lines can be drawn on a paint
+ device.
+
+ Here's some pseudo code that presents the layout phase:
+ \snippet doc/src/snippets/code/src_gui_text_qtextlayout.cpp 0
+
+ The text can be drawn by calling the layout's draw() function:
+ \snippet doc/src/snippets/code/src_gui_text_qtextlayout.cpp 1
+
+ The text layout's text is set in the constructor or with
+ setText(). The layout can be seen as a sequence of QTextLine
+ objects; use lineAt() or lineForTextPosition() to get a QTextLine,
+ createLine() to create one. For a given position in the text you
+ can find a valid cursor position with isValidCursorPosition(),
+ nextCursorPosition(), and previousCursorPosition(). The layout
+ itself can be positioned with setPosition(); it has a
+ boundingRect(), and a minimumWidth() and a maximumWidth(). A text
+ layout can be drawn on a painter device using draw().
+
+*/
+
+/*!
+ \enum QTextLayout::CursorMode
+
+ \value SkipCharacters
+ \value SkipWords
+*/
+
+/*!
+ \fn QTextEngine *QTextLayout::engine() const
+ \internal
+
+ Returns the text engine used to render the text layout.
+*/
+
+/*!
+ Constructs an empty text layout.
+
+ \sa setText()
+*/
+QTextLayout::QTextLayout()
+{ d = new QTextEngine(); }
+
+/*!
+ Constructs a text layout to lay out the given \a text.
+*/
+QTextLayout::QTextLayout(const QString& text)
+{
+ d = new QTextEngine();
+ d->text = text;
+}
+
+/*!
+ Constructs a text layout to lay out the given \a text with the specified
+ \a font.
+
+ All the metric and layout calculations will be done in terms of
+ the paint device, \a paintdevice. If \a paintdevice is 0 the
+ calculations will be done in screen metrics.
+*/
+QTextLayout::QTextLayout(const QString& text, const QFont &font, QPaintDevice *paintdevice)
+{
+ QFont f(font);
+ if (paintdevice)
+ f = QFont(font, paintdevice);
+ d = new QTextEngine((text.isNull() ? (const QString&)QString::fromLatin1("") : text), f.d);
+}
+
+/*!
+ \internal
+ Constructs a text layout to lay out the given \a block.
+*/
+QTextLayout::QTextLayout(const QTextBlock &block)
+{
+ d = new QTextEngine();
+ d->block = block;
+}
+
+/*!
+ Destructs the layout.
+*/
+QTextLayout::~QTextLayout()
+{
+ if (!d->stackEngine)
+ delete d;
+}
+
+/*!
+ Sets the layout's font to the given \a font. The layout is
+ invalidated and must be laid out again.
+
+ \sa text()
+*/
+void QTextLayout::setFont(const QFont &font)
+{
+ d->fnt = font;
+}
+
+/*!
+ Returns the current font that is used for the layout, or a default
+ font if none is set.
+*/
+QFont QTextLayout::font() const
+{
+ return d->font();
+}
+
+/*!
+ Sets the layout's text to the given \a string. The layout is
+ invalidated and must be laid out again.
+
+ Notice that when using this QTextLayout as part of a QTextDocument this
+ method will have no effect.
+
+ \sa text()
+*/
+void QTextLayout::setText(const QString& string)
+{
+ d->invalidate();
+ d->clearLineData();
+ d->text = string;
+}
+
+/*!
+ Returns the layout's text.
+
+ \sa setText()
+*/
+QString QTextLayout::text() const
+{
+ return d->text;
+}
+
+/*!
+ Sets the text option structure that controls the layout process to the
+ given \a option.
+
+ \sa textOption() QTextOption
+*/
+void QTextLayout::setTextOption(const QTextOption &option)
+{
+ d->option = option;
+}
+
+/*!
+ Returns the current text option used to control the layout process.
+
+ \sa setTextOption() QTextOption
+*/
+QTextOption QTextLayout::textOption() const
+{
+ return d->option;
+}
+
+/*!
+ Sets the \a position and \a text of the area in the layout that is
+ processed before editing occurs.
+*/
+void QTextLayout::setPreeditArea(int position, const QString &text)
+{
+ if (text.isEmpty()) {
+ if (!d->specialData)
+ return;
+ if (d->specialData->addFormats.isEmpty()) {
+ delete d->specialData;
+ d->specialData = 0;
+ } else {
+ d->specialData->preeditText = QString();
+ d->specialData->preeditPosition = -1;
+ }
+ } else {
+ if (!d->specialData)
+ d->specialData = new QTextEngine::SpecialData;
+ d->specialData->preeditPosition = position;
+ d->specialData->preeditText = text;
+ }
+ d->invalidate();
+ d->clearLineData();
+ if (d->block.docHandle())
+ d->block.docHandle()->documentChange(d->block.position(), d->block.length());
+}
+
+/*!
+ Returns the position of the area in the text layout that will be
+ processed before editing occurs.
+*/
+int QTextLayout::preeditAreaPosition() const
+{
+ return d->specialData ? d->specialData->preeditPosition : -1;
+}
+
+/*!
+ Returns the text that is inserted in the layout before editing occurs.
+*/
+QString QTextLayout::preeditAreaText() const
+{
+ return d->specialData ? d->specialData->preeditText : QString();
+}
+
+
+/*!
+ Sets the additional formats supported by the text layout to \a
+ formatList.
+
+ \sa additionalFormats(), clearAdditionalFormats()
+*/
+void QTextLayout::setAdditionalFormats(const QList<FormatRange> &formatList)
+{
+ if (formatList.isEmpty()) {
+ if (!d->specialData)
+ return;
+ if (d->specialData->preeditText.isEmpty()) {
+ delete d->specialData;
+ d->specialData = 0;
+ } else {
+ d->specialData->addFormats = formatList;
+ d->specialData->addFormatIndices.clear();
+ }
+ } else {
+ if (!d->specialData) {
+ d->specialData = new QTextEngine::SpecialData;
+ d->specialData->preeditPosition = -1;
+ }
+ d->specialData->addFormats = formatList;
+ d->indexAdditionalFormats();
+ }
+ if (d->block.docHandle())
+ d->block.docHandle()->documentChange(d->block.position(), d->block.length());
+}
+
+/*!
+ Returns the list of additional formats supported by the text layout.
+
+ \sa setAdditionalFormats(), clearAdditionalFormats()
+*/
+QList<QTextLayout::FormatRange> QTextLayout::additionalFormats() const
+{
+ QList<FormatRange> formats;
+ if (!d->specialData)
+ return formats;
+
+ formats = d->specialData->addFormats;
+
+ if (d->specialData->addFormatIndices.isEmpty())
+ return formats;
+
+ const QTextFormatCollection *collection = d->formats();
+
+ for (int i = 0; i < d->specialData->addFormatIndices.count(); ++i)
+ formats[i].format = collection->charFormat(d->specialData->addFormatIndices.at(i));
+
+ return formats;
+}
+
+/*!
+ Clears the list of additional formats supported by the text layout.
+
+ \sa additionalFormats(), setAdditionalFormats()
+*/
+void QTextLayout::clearAdditionalFormats()
+{
+ setAdditionalFormats(QList<FormatRange>());
+}
+
+/*!
+ Enables caching of the complete layout information if \a enable is
+ true; otherwise disables layout caching. Usually
+ QTextLayout throws most of the layouting information away after a
+ call to endLayout() to reduce memory consumption. If you however
+ want to draw the laid out text directly afterwards enabling caching
+ might speed up drawing significantly.
+
+ \sa cacheEnabled()
+*/
+void QTextLayout::setCacheEnabled(bool enable)
+{
+ d->cacheGlyphs = enable;
+}
+
+/*!
+ Returns true if the complete layout information is cached; otherwise
+ returns false.
+
+ \sa setCacheEnabled()
+*/
+bool QTextLayout::cacheEnabled() const
+{
+ return d->cacheGlyphs;
+}
+
+/*!
+ Begins the layout process.
+*/
+void QTextLayout::beginLayout()
+{
+#ifndef QT_NO_DEBUG
+ if (d->layoutData && d->layoutData->inLayout) {
+ qWarning("QTextLayout::beginLayout: Called while already doing layout");
+ return;
+ }
+#endif
+ d->invalidate();
+ d->clearLineData();
+ d->itemize();
+ d->layoutData->inLayout = true;
+}
+
+/*!
+ Ends the layout process.
+*/
+void QTextLayout::endLayout()
+{
+#ifndef QT_NO_DEBUG
+ if (!d->layoutData || !d->layoutData->inLayout) {
+ qWarning("QTextLayout::endLayout: Called without beginLayout()");
+ return;
+ }
+#endif
+ int l = d->lines.size();
+ if (l && d->lines.at(l-1).length < 0) {
+ QTextLine(l-1, d).setNumColumns(INT_MAX);
+ }
+ d->layoutData->inLayout = false;
+ if (!d->cacheGlyphs)
+ d->freeMemory();
+}
+
+/*! \since 4.4
+
+Clears the line information in the layout. After having called
+this function, lineCount() returns 0.
+ */
+void QTextLayout::clearLayout()
+{
+ d->clearLineData();
+}
+
+
+/*!
+ Returns the next valid cursor position after \a oldPos that
+ respects the given cursor \a mode.
+
+ \sa isValidCursorPosition() previousCursorPosition()
+*/
+int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const
+{
+// qDebug("looking for next cursor pos for %d", oldPos);
+ const HB_CharAttributes *attributes = d->attributes();
+ if (!attributes)
+ return 0;
+ int len = d->block.isValid() ?
+ (d->block.length() - 1)
+ : d->layoutData->string.length();
+
+ if (oldPos >= len)
+ return oldPos;
+ if (mode == SkipCharacters) {
+ oldPos++;
+ while (oldPos < len && !attributes[oldPos].charStop)
+ oldPos++;
+ } else {
+ if (oldPos < len && d->atWordSeparator(oldPos)) {
+ oldPos++;
+ while (oldPos < len && d->atWordSeparator(oldPos))
+ oldPos++;
+ } else {
+ while (oldPos < len && !d->atSpace(oldPos) && !d->atWordSeparator(oldPos))
+ oldPos++;
+ }
+ while (oldPos < len && d->atSpace(oldPos))
+ oldPos++;
+ }
+// qDebug(" -> %d", oldPos);
+ return oldPos;
+}
+
+/*!
+ Returns the first valid cursor position before \a oldPos that
+ respects the given cursor \a mode.
+
+ \sa isValidCursorPosition() nextCursorPosition()
+*/
+int QTextLayout::previousCursorPosition(int oldPos, CursorMode mode) const
+{
+// qDebug("looking for previous cursor pos for %d", oldPos);
+ const HB_CharAttributes *attributes = d->attributes();
+ if (!attributes || oldPos <= 0)
+ return 0;
+ if (mode == SkipCharacters) {
+ oldPos--;
+ while (oldPos && !attributes[oldPos].charStop)
+ oldPos--;
+ } else {
+ while (oldPos && d->atSpace(oldPos-1))
+ oldPos--;
+
+ if (oldPos && d->atWordSeparator(oldPos-1)) {
+ oldPos--;
+ while (oldPos && d->atWordSeparator(oldPos-1))
+ oldPos--;
+ } else {
+ while (oldPos && !d->atSpace(oldPos-1) && !d->atWordSeparator(oldPos-1))
+ oldPos--;
+ }
+ }
+// qDebug(" -> %d", oldPos);
+ return oldPos;
+}
+
+/*!
+ Returns true if position \a pos is a valid cursor position.
+
+ In a Unicode context some positions in the text are not valid
+ cursor positions, because the position is inside a Unicode
+ surrogate or a grapheme cluster.
+
+ A grapheme cluster is a sequence of two or more Unicode characters
+ that form one indivisible entity on the screen. For example the
+ latin character `\Auml' can be represented in Unicode by two
+ characters, `A' (0x41), and the combining diaresis (0x308). A text
+ cursor can only validly be positioned before or after these two
+ characters, never between them since that wouldn't make sense. In
+ indic languages every syllable forms a grapheme cluster.
+*/
+bool QTextLayout::isValidCursorPosition(int pos) const
+{
+ const HB_CharAttributes *attributes = d->attributes();
+ if (!attributes || pos < 0 || pos > (int)d->layoutData->string.length())
+ return false;
+ return attributes[pos].charStop;
+}
+
+
+/*!
+ Returns a new text line to be laid out if there is text to be
+ inserted into the layout; otherwise returns an invalid text line.
+
+ The text layout creates a new line object that starts after the
+ last line in the layout, or at the beginning if the layout is empty.
+ The layout maintains an internal cursor, and each line is filled
+ with text from the cursor position onwards when the
+ QTextLine::setLineWidth() function is called.
+
+ Once QTextLine::setLineWidth() is called, a new line can be created and
+ filled with text. Repeating this process will lay out the whole block
+ of text contained in the QTextLayout. If there is no text left to be
+ inserted into the layout, the QTextLine returned will not be valid
+ (isValid() will return false).
+*/
+QTextLine QTextLayout::createLine()
+{
+#ifndef QT_NO_DEBUG
+ if (!d->layoutData || !d->layoutData->inLayout) {
+ qWarning("QTextLayout::createLine: Called without layouting");
+ return QTextLine();
+ }
+#endif
+ int l = d->lines.size();
+ if (l && d->lines.at(l-1).length < 0) {
+ QTextLine(l-1, d).setNumColumns(INT_MAX);
+ }
+ int from = l > 0 ? d->lines.at(l-1).from + d->lines.at(l-1).length : 0;
+ int strlen = d->layoutData->string.length();
+ if (l && from >= strlen) {
+ if (!d->lines.at(l-1).length || d->layoutData->string.at(strlen - 1) != QChar::LineSeparator)
+ return QTextLine();
+ }
+
+ QScriptLine line;
+ line.from = from;
+ line.length = -1;
+ line.justified = false;
+ line.gridfitted = false;
+
+ d->lines.append(line);
+ return QTextLine(l, d);
+}
+
+/*!
+ Returns the number of lines in this text layout.
+
+ \sa lineAt()
+*/
+int QTextLayout::lineCount() const
+{
+ return d->lines.size();
+}
+
+/*!
+ Returns the \a{i}-th line of text in this text layout.
+
+ \sa lineCount() lineForTextPosition()
+*/
+QTextLine QTextLayout::lineAt(int i) const
+{
+ return QTextLine(i, d);
+}
+
+/*!
+ Returns the line that contains the cursor position specified by \a pos.
+
+ \sa isValidCursorPosition() lineAt()
+*/
+QTextLine QTextLayout::lineForTextPosition(int pos) const
+{
+ for (int i = 0; i < d->lines.size(); ++i) {
+ const QScriptLine& line = d->lines[i];
+ if (line.from + (int)line.length > pos)
+ return QTextLine(i, d);
+ }
+ if (!d->layoutData)
+ d->itemize();
+ if (pos == d->layoutData->string.length() && d->lines.size())
+ return QTextLine(d->lines.size()-1, d);
+ return QTextLine();
+}
+
+/*!
+ \since 4.2
+
+ The global position of the layout. This is independent of the
+ bounding rectangle and of the layout process.
+
+ \sa setPosition()
+*/
+QPointF QTextLayout::position() const
+{
+ return d->position;
+}
+
+/*!
+ Moves the text layout to point \a p.
+
+ \sa position()
+*/
+void QTextLayout::setPosition(const QPointF &p)
+{
+ d->position = p;
+}
+
+/*!
+ The smallest rectangle that contains all the lines in the layout.
+*/
+QRectF QTextLayout::boundingRect() const
+{
+ if (d->lines.isEmpty())
+ return QRectF();
+
+ QFixed xmax, ymax;
+ QFixed xmin = d->lines.at(0).x;
+ QFixed ymin = d->lines.at(0).y;
+
+ for (int i = 0; i < d->lines.size(); ++i) {
+ const QScriptLine &si = d->lines[i];
+ xmin = qMin(xmin, si.x);
+ ymin = qMin(ymin, si.y);
+ xmax = qMax(xmax, si.x+qMax(si.width, si.textWidth));
+ // ### shouldn't the ascent be used in ymin???
+ ymax = qMax(ymax, si.y+si.ascent+si.descent+1);
+ }
+ return QRectF(xmin.toReal(), ymin.toReal(), (xmax-xmin).toReal(), (ymax-ymin).toReal());
+}
+
+/*!
+ The minimum width the layout needs. This is the width of the
+ layout's smallest non-breakable substring.
+
+ \warning This function only returns a valid value after the layout
+ has been done.
+
+ \sa maximumWidth()
+*/
+qreal QTextLayout::minimumWidth() const
+{
+ return d->minWidth.toReal();
+}
+
+/*!
+ The maximum width the layout could expand to; this is essentially
+ the width of the entire text.
+
+ \warning This function only returns a valid value after the layout
+ has been done.
+
+ \sa minimumWidth()
+*/
+qreal QTextLayout::maximumWidth() const
+{
+ return d->maxWidth.toReal();
+}
+
+/*!
+ \internal
+*/
+void QTextLayout::setFlags(int flags)
+{
+ if (flags & Qt::TextJustificationForced) {
+ d->option.setAlignment(Qt::AlignJustify);
+ d->forceJustification = true;
+ }
+
+ if (flags & (Qt::TextForceLeftToRight|Qt::TextForceRightToLeft)) {
+ d->ignoreBidi = true;
+ d->option.setTextDirection((flags & Qt::TextForceLeftToRight) ? Qt::LeftToRight : Qt::RightToLeft);
+ }
+}
+
+struct QTextLineItemIterator
+{
+ QTextLineItemIterator(QTextEngine *eng, int lineNum, const QPointF &pos = QPointF(),
+ const QTextLayout::FormatRange *_selection = 0);
+
+ inline bool atEnd() const { return logicalItem >= nItems - 1; }
+ QScriptItem &next();
+
+ bool getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const;
+ inline bool isOutsideSelection() const {
+ QFixed tmp1, tmp2;
+ return !getSelectionBounds(&tmp1, &tmp2);
+ }
+
+ QTextEngine *eng;
+
+ QFixed x;
+ QFixed pos_x;
+ const QScriptLine &line;
+ QScriptItem *si;
+
+ int lineEnd;
+ int firstItem;
+ int lastItem;
+ int nItems;
+ int logicalItem;
+ int item;
+ int itemLength;
+
+ int glyphsStart;
+ int glyphsEnd;
+ int itemStart;
+ int itemEnd;
+
+ QFixed itemWidth;
+
+ QVarLengthArray<int> visualOrder;
+ QVarLengthArray<uchar> levels;
+
+ const QTextLayout::FormatRange *selection;
+};
+
+QTextLineItemIterator::QTextLineItemIterator(QTextEngine *_eng, int lineNum, const QPointF &pos,
+ const QTextLayout::FormatRange *_selection)
+ : eng(_eng),
+ line(eng->lines[lineNum]),
+ si(0),
+ lineEnd(line.from + line.length),
+ firstItem(eng->findItem(line.from)),
+ lastItem(eng->findItem(lineEnd - 1)),
+ nItems((firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0),
+ logicalItem(-1),
+ item(-1),
+ visualOrder(nItems),
+ levels(nItems),
+ selection(_selection)
+{
+ pos_x = x = QFixed::fromReal(pos.x());
+
+ x += line.x;
+
+ x += alignLine(eng, line);
+
+ for (int i = 0; i < nItems; ++i)
+ levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
+ QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
+
+ eng->shapeLine(line);
+}
+
+QScriptItem &QTextLineItemIterator::next()
+{
+ x += itemWidth;
+
+ ++logicalItem;
+ item = visualOrder[logicalItem] + firstItem;
+ itemLength = eng->length(item);
+ si = &eng->layoutData->items[item];
+ if (!si->num_glyphs)
+ eng->shape(item);
+
+ if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
+ itemWidth = si->width;
+ return *si;
+ }
+
+ unsigned short *logClusters = eng->logClusters(si);
+ QGlyphLayout glyphs = eng->shapedGlyphs(si);
+
+ itemStart = qMax(line.from, si->position);
+ glyphsStart = logClusters[itemStart - si->position];
+ if (lineEnd < si->position + itemLength) {
+ itemEnd = lineEnd;
+ glyphsEnd = logClusters[itemEnd-si->position];
+ } else {
+ itemEnd = si->position + itemLength;
+ glyphsEnd = si->num_glyphs;
+ }
+ // show soft-hyphen at line-break
+ if (si->position + itemLength >= lineEnd
+ && eng->layoutData->string.at(lineEnd - 1) == 0x00ad)
+ glyphs.attributes[glyphsEnd - 1].dontPrint = false;
+
+ itemWidth = 0;
+ for (int g = glyphsStart; g < glyphsEnd; ++g)
+ itemWidth += glyphs.effectiveAdvance(g);
+
+ return *si;
+}
+
+bool QTextLineItemIterator::getSelectionBounds(QFixed *selectionX, QFixed *selectionWidth) const
+{
+ *selectionX = *selectionWidth = 0;
+
+ if (!selection)
+ return false;
+
+ if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
+ if (si->position >= selection->start + selection->length
+ || si->position + itemLength <= selection->start)
+ return false;
+
+ *selectionX = x;
+ *selectionWidth = itemWidth;
+ } else {
+ unsigned short *logClusters = eng->logClusters(si);
+ QGlyphLayout glyphs = eng->shapedGlyphs(si);
+
+ int from = qMax(itemStart, selection->start) - si->position;
+ int to = qMin(itemEnd, selection->start + selection->length) - si->position;
+ if (from >= to)
+ return false;
+
+ int start_glyph = logClusters[from];
+ int end_glyph = (to == eng->length(item)) ? si->num_glyphs : logClusters[to];
+ QFixed soff;
+ QFixed swidth;
+ if (si->analysis.bidiLevel %2) {
+ for (int g = glyphsEnd - 1; g >= end_glyph; --g)
+ soff += glyphs.effectiveAdvance(g);
+ for (int g = end_glyph - 1; g >= start_glyph; --g)
+ swidth += glyphs.effectiveAdvance(g);
+ } else {
+ for (int g = glyphsStart; g < start_glyph; ++g)
+ soff += glyphs.effectiveAdvance(g);
+ for (int g = start_glyph; g < end_glyph; ++g)
+ swidth += glyphs.effectiveAdvance(g);
+ }
+
+ *selectionX = x + soff;
+ *selectionWidth = swidth;
+ }
+ return true;
+}
+
+static void addSelectedRegionsToPath(QTextEngine *eng, int lineNumber, const QPointF &pos, QTextLayout::FormatRange *selection,
+ QPainterPath *region, QRectF boundingRect)
+{
+ const QScriptLine &line = eng->lines[lineNumber];
+
+ QTextLineItemIterator iterator(eng, lineNumber, pos, selection);
+
+ const QFixed y = QFixed::fromReal(pos.y()) + line.y + line.ascent;
+
+ const qreal lineHeight = line.height().toReal();
+ const qreal selectionY = (y - line.ascent).toReal();
+
+ QFixed lastSelectionX = iterator.x;
+ QFixed lastSelectionWidth;
+
+ while (!iterator.atEnd()) {
+ iterator.next();
+
+ QFixed selectionX, selectionWidth;
+ if (iterator.getSelectionBounds(&selectionX, &selectionWidth)) {
+ if (selectionX == lastSelectionX + lastSelectionWidth) {
+ lastSelectionWidth += selectionWidth;
+ continue;
+ }
+
+ if (lastSelectionWidth > 0)
+ region->addRect(boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight));
+
+ lastSelectionX = selectionX;
+ lastSelectionWidth = selectionWidth;
+ }
+ }
+ if (lastSelectionWidth > 0)
+ region->addRect(boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight));
+}
+
+static inline QRectF clipIfValid(const QRectF &rect, const QRectF &clip)
+{
+ return clip.isValid() ? (rect & clip) : rect;
+}
+
+/*!
+ Draws the whole layout on the painter \a p at the position specified by
+ \a pos.
+ The rendered layout includes the given \a selections and is clipped within
+ the rectangle specified by \a clip.
+*/
+void QTextLayout::draw(QPainter *p, const QPointF &pos, const QVector<FormatRange> &selections, const QRectF &clip) const
+{
+ if (d->lines.isEmpty())
+ return;
+
+ if (!d->layoutData)
+ d->itemize();
+
+ QPointF position = pos + d->position;
+
+ QFixed clipy = (INT_MIN/256);
+ QFixed clipe = (INT_MAX/256);
+ if (clip.isValid()) {
+ clipy = QFixed::fromReal(clip.y() - position.y());
+ clipe = clipy + QFixed::fromReal(clip.height());
+ }
+
+ int firstLine = 0;
+ int lastLine = d->lines.size();
+ for (int i = 0; i < d->lines.size(); ++i) {
+ QTextLine l(i, d);
+ const QScriptLine &sl = d->lines[i];
+
+ if (sl.y > clipe) {
+ lastLine = i;
+ break;
+ }
+ if ((sl.y + sl.height()) < clipy) {
+ firstLine = i;
+ continue;
+ }
+ }
+
+ QPainterPath excludedRegion;
+ for (int i = 0; i < selections.size(); ++i) {
+ FormatRange selection = selections.at(i);
+ const QBrush bg = selection.format.background();
+
+ QPainterPath region;
+ region.setFillRule(Qt::WindingFill);
+
+ for (int line = firstLine; line < lastLine; ++line) {
+ const QScriptLine &sl = d->lines[line];
+ QTextLine tl(line, d);
+
+ QRectF lineRect(tl.naturalTextRect());
+ lineRect.translate(position);
+
+ bool isLastLineInBlock = (line == d->lines.size()-1);
+ int sl_length = sl.length + (isLastLineInBlock? 1 : 0); // the infamous newline
+
+
+ if (sl.from > selection.start + selection.length || sl.from + sl_length <= selection.start)
+ continue; // no actual intersection
+
+ const bool selectionStartInLine = sl.from <= selection.start;
+ const bool selectionEndInLine = selection.start + selection.length < sl.from + sl_length;
+
+ if (sl.length && (selectionStartInLine || selectionEndInLine)) {
+ addSelectedRegionsToPath(d, line, position, &selection, &region, clipIfValid(lineRect, clip));
+ } else {
+ region.addRect(clipIfValid(lineRect, clip));
+ }
+
+ if (selection.format.boolProperty(QTextFormat::FullWidthSelection)) {
+ QRectF fullLineRect(tl.rect());
+ fullLineRect.translate(position);
+ fullLineRect.setRight(QFIXED_MAX);
+ if (!selectionEndInLine)
+ region.addRect(clipIfValid(QRectF(lineRect.topRight(), fullLineRect.bottomRight()), clip));
+ if (!selectionStartInLine)
+ region.addRect(clipIfValid(QRectF(fullLineRect.topLeft(), lineRect.bottomLeft()), clip));
+ } else if (!selectionEndInLine
+ && isLastLineInBlock
+ &&!(d->option.flags() & QTextOption::ShowLineAndParagraphSeparators)) {
+ region.addRect(clipIfValid(QRectF(lineRect.right(), lineRect.top(),
+ lineRect.height()/4, lineRect.height()), clip));
+ }
+
+ }
+ {
+ const QPen oldPen = p->pen();
+ const QBrush oldBrush = p->brush();
+
+ p->setPen(selection.format.penProperty(QTextFormat::OutlinePen));
+ p->setBrush(selection.format.brushProperty(QTextFormat::BackgroundBrush));
+ p->drawPath(region);
+
+ p->setPen(oldPen);
+ p->setBrush(oldBrush);
+ }
+
+
+ p->save();
+ p->setClipPath(region, Qt::IntersectClip);
+
+ selection.format.setProperty(ObjectSelectionBrush, selection.format.property(QTextFormat::BackgroundBrush));
+ // don't just clear the property, set an empty brush that overrides a potential
+ // background brush specified in the text
+ selection.format.setProperty(QTextFormat::BackgroundBrush, QBrush());
+ selection.format.clearProperty(QTextFormat::OutlinePen);
+
+ for (int line = firstLine; line < lastLine; ++line) {
+ QTextLine l(line, d);
+ l.draw(p, position, &selection);
+ }
+ p->restore();
+
+ if (selection.format.foreground().style() != Qt::NoBrush) // i.e. we have drawn text
+ excludedRegion += region;
+ }
+
+ if (!excludedRegion.isEmpty()) {
+ p->save();
+ QPainterPath path;
+ QRectF br = boundingRect().translated(position);
+ br.setRight(QFIXED_MAX);
+ if (!clip.isNull())
+ br = br.intersected(clip);
+ path.addRect(br);
+ path -= excludedRegion;
+ p->setClipPath(path, Qt::IntersectClip);
+ }
+
+ for (int i = firstLine; i < lastLine; ++i) {
+ QTextLine l(i, d);
+ l.draw(p, position);
+ }
+ if (!excludedRegion.isEmpty())
+ p->restore();
+
+
+ if (!d->cacheGlyphs)
+ d->freeMemory();
+}
+
+/*!
+ \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition) const
+ \overload
+
+ Draws a text cursor with the current pen at the given \a position using the
+ \a painter specified.
+ The corresponding position within the text is specified by \a cursorPosition.
+*/
+void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const
+{
+ drawCursor(p, pos, cursorPosition, 1);
+}
+
+/*!
+ \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition, int width) const
+
+ Draws a text cursor with the current pen and the specified \a width at the given \a position using the
+ \a painter specified.
+ The corresponding position within the text is specified by \a cursorPosition.
+*/
+void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const
+{
+ if (d->lines.isEmpty())
+ return;
+
+ if (!d->layoutData)
+ d->itemize();
+
+ QPointF position = pos + d->position;
+ QFixed pos_x = QFixed::fromReal(position.x());
+ QFixed pos_y = QFixed::fromReal(position.y());
+
+ cursorPosition = qBound(0, cursorPosition, d->layoutData->string.length());
+ int line = 0;
+ if (cursorPosition == d->layoutData->string.length()) {
+ line = d->lines.size() - 1;
+ } else {
+ // ### binary search
+ for (line = 0; line < d->lines.size(); line++) {
+ const QScriptLine &sl = d->lines[line];
+ if (sl.from <= cursorPosition && sl.from + (int)sl.length > cursorPosition)
+ break;
+ }
+ }
+
+ if (line >= d->lines.size())
+ return;
+
+ QTextLine l(line, d);
+ const QScriptLine &sl = d->lines[line];
+
+ const qreal x = position.x() + l.cursorToX(cursorPosition);
+
+ int itm = d->findItem(cursorPosition - 1);
+ QFixed ascent = sl.ascent;
+ QFixed descent = sl.descent;
+ bool rightToLeft = (d->option.textDirection() == Qt::RightToLeft);
+ if (itm >= 0) {
+ const QScriptItem &si = d->layoutData->items.at(itm);
+ if (si.ascent > 0)
+ ascent = si.ascent;
+ if (si.descent > 0)
+ descent = si.descent;
+ rightToLeft = si.analysis.bidiLevel % 2;
+ }
+ qreal y = position.y() + (sl.y + sl.ascent - ascent).toReal();
+ bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing)
+ && (p->transform().type() > QTransform::TxTranslate);
+ if (toggleAntialiasing)
+ p->setRenderHint(QPainter::Antialiasing);
+ p->fillRect(QRectF(x, y, qreal(width), (ascent + descent).toReal()), p->pen().brush());
+ if (toggleAntialiasing)
+ p->setRenderHint(QPainter::Antialiasing, false);
+ if (d->layoutData->hasBidi) {
+ const int arrow_extent = 4;
+ int sign = rightToLeft ? -1 : 1;
+ p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2));
+ p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2));
+ }
+ return;
+}
+
+/*!
+ \class QTextLine
+ \reentrant
+
+ \brief The QTextLine class represents a line of text inside a QTextLayout.
+
+ \ingroup text
+
+ A text line is usually created by QTextLayout::createLine().
+
+ After being created, the line can be filled using the setLineWidth()
+ or setNumColumns() functions. A line has a number of attributes including the
+ rectangle it occupies, rect(), its coordinates, x() and y(), its
+ textLength(), width() and naturalTextWidth(), and its ascent() and decent()
+ relative to the text. The position of the cursor in terms of the
+ line is available from cursorToX() and its inverse from
+ xToCursor(). A line can be moved with setPosition().
+*/
+
+/*!
+ \enum QTextLine::Edge
+
+ \value Leading
+ \value Trailing
+*/
+
+/*!
+ \enum QTextLine::CursorPosition
+
+ \value CursorBetweenCharacters
+ \value CursorOnCharacter
+*/
+
+/*!
+ \fn QTextLine::QTextLine(int line, QTextEngine *e)
+ \internal
+
+ Constructs a new text line using the line at position \a line in
+ the text engine \a e.
+*/
+
+/*!
+ \fn QTextLine::QTextLine()
+
+ Creates an invalid line.
+*/
+
+/*!
+ \fn bool QTextLine::isValid() const
+
+ Returns true if this text line is valid; otherwise returns false.
+*/
+
+/*!
+ \fn int QTextLine::lineNumber() const
+
+ Returns the position of the line in the text engine.
+*/
+
+
+/*!
+ Returns the line's bounding rectangle.
+
+ \sa x() y() textLength() width()
+*/
+QRectF QTextLine::rect() const
+{
+ const QScriptLine& sl = eng->lines[i];
+ return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal());
+}
+
+/*!
+ Returns the rectangle covered by the line.
+*/
+QRectF QTextLine::naturalTextRect() const
+{
+ const QScriptLine& sl = eng->lines[i];
+ QFixed x = sl.x + alignLine(eng, sl);
+
+ QFixed width = sl.textWidth;
+ if (sl.justified)
+ width = sl.width;
+
+ return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal());
+}
+
+/*!
+ Returns the line's x position.
+
+ \sa rect() y() textLength() width()
+*/
+qreal QTextLine::x() const
+{
+ return eng->lines[i].x.toReal();
+}
+
+/*!
+ Returns the line's y position.
+
+ \sa x() rect() textLength() width()
+*/
+qreal QTextLine::y() const
+{
+ return eng->lines[i].y.toReal();
+}
+
+/*!
+ Returns the line's width as specified by the layout() function.
+
+ \sa naturalTextWidth() x() y() textLength() rect()
+*/
+qreal QTextLine::width() const
+{
+ return eng->lines[i].width.toReal();
+}
+
+
+/*!
+ Returns the line's ascent.
+
+ \sa descent() height()
+*/
+qreal QTextLine::ascent() const
+{
+ return eng->lines[i].ascent.toReal();
+}
+
+/*!
+ Returns the line's descent.
+
+ \sa ascent() height()
+*/
+qreal QTextLine::descent() const
+{
+ return eng->lines[i].descent.toReal();
+}
+
+/*!
+ Returns the line's height. This is equal to ascent() + descent() + 1.
+
+ \sa ascent() descent()
+*/
+qreal QTextLine::height() const
+{
+ return eng->lines[i].height().toReal();
+}
+
+/*!
+ Returns the width of the line that is occupied by text. This is
+ always \<= to width(), and is the minimum width that could be used
+ by layout() without changing the line break position.
+*/
+qreal QTextLine::naturalTextWidth() const
+{
+ return eng->lines[i].textWidth.toReal();
+}
+
+/*!
+ Lays out the line with the given \a width. The line is filled from
+ its starting position with as many characters as will fit into
+ the line. In case the text cannot be split at the end of the line,
+ it will be filled with additional characters to the next whitespace
+ or end of the text.
+*/
+void QTextLine::setLineWidth(qreal width)
+{
+ QScriptLine &line = eng->lines[i];
+ if (!eng->layoutData) {
+ qWarning("QTextLine: Can't set a line width while not layouting.");
+ return;
+ }
+
+ if (width > QFIXED_MAX)
+ width = QFIXED_MAX;
+
+ line.width = QFixed::fromReal(width);
+ if (line.length
+ && line.textWidth <= line.width
+ && line.from + line.length == eng->layoutData->string.length())
+ // no need to do anything if the line is already layouted and the last one. This optimisation helps
+ // when using things in a single line layout.
+ return;
+ line.length = 0;
+ line.textWidth = 0;
+
+ layout_helper(INT_MAX);
+}
+
+/*!
+ Lays out the line. The line is filled from its starting position
+ with as many characters as are specified by \a numColumns. In case
+ the text cannot be split until \a numColumns characters, the line
+ will be filled with as many characters to the next whitespace or
+ end of the text.
+*/
+void QTextLine::setNumColumns(int numColumns)
+{
+ QScriptLine &line = eng->lines[i];
+ line.width = QFIXED_MAX;
+ line.length = 0;
+ line.textWidth = 0;
+ layout_helper(numColumns);
+}
+
+/*!
+ Lays out the line. The line is filled from its starting position
+ with as many characters as are specified by \a numColumns. In case
+ the text cannot be split until \a numColumns characters, the line
+ will be filled with as many characters to the next whitespace or
+ end of the text. The provided \a alignmentWidth is used as reference
+ width for alignment.
+*/
+void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
+{
+ QScriptLine &line = eng->lines[i];
+ line.width = QFixed::fromReal(alignmentWidth);
+ line.length = 0;
+ line.textWidth = 0;
+ layout_helper(numColumns);
+}
+
+#if 0
+#define LB_DEBUG qDebug
+#else
+#define LB_DEBUG if (0) qDebug
+#endif
+
+static inline bool checkFullOtherwiseExtend(QScriptLine &line, QScriptLine &tmpData, QScriptLine &spaceData,
+ int glyphCount, int maxGlyphs, QFixed &minw, bool manualWrap,
+ QFixed softHyphenWidth = QFixed())
+{
+ LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal());
+ if (line.length && !manualWrap &&
+ (line.textWidth + tmpData.textWidth + spaceData.textWidth + softHyphenWidth > line.width || glyphCount > maxGlyphs))
+ return true;
+ minw = qMax(minw, tmpData.textWidth);
+ line += tmpData;
+ line.textWidth += spaceData.textWidth;
+ line.length += spaceData.length;
+ tmpData.textWidth = 0;
+ tmpData.length = 0;
+ spaceData.textWidth = 0;
+ spaceData.length = 0;
+ return false;
+}
+
+static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount,
+ const QScriptItem &current, const unsigned short *logClusters, const QGlyphLayout &glyphs)
+{
+ int glyphPosition = logClusters[pos];
+ do { // got to the first next cluster
+ ++pos;
+ ++line.length;
+ } while (pos < end && logClusters[pos] == glyphPosition);
+ do { // calculate the textWidth for the rest of the current cluster.
+ line.textWidth += glyphs.advances_x[glyphPosition] * !glyphs.attributes[glyphPosition].dontPrint;
+ ++glyphPosition;
+ } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
+
+ Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
+
+ ++glyphCount;
+}
+
+
+// fill QScriptLine
+void QTextLine::layout_helper(int maxGlyphs)
+{
+ QScriptLine &line = eng->lines[i];
+ line.length = 0;
+ line.textWidth = 0;
+ line.hasTrailingSpaces = false;
+
+ if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.length()) {
+ line.setDefaultHeight(eng);
+ return;
+ }
+
+ Q_ASSERT(line.from < eng->layoutData->string.length());
+
+ QTextOption::WrapMode wrapMode = eng->option.wrapMode();
+ bool breakany = (wrapMode == QTextOption::WrapAnywhere);
+ bool manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap);
+
+ // #### binary search!
+ int item = -1;
+ int newItem;
+ for (newItem = eng->layoutData->items.size()-1; newItem > 0; --newItem) {
+ if (eng->layoutData->items[newItem].position <= line.from)
+ break;
+ }
+
+ QFixed minw = 0;
+ int glyphCount = 0;
+
+ LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, eng->layoutData->items.size(), line.width.toReal());
+ QScriptLine tmpData;
+ QScriptLine spaceData;
+
+ Qt::Alignment alignment = eng->option.alignment();
+
+ const HB_CharAttributes *attributes = eng->attributes();
+ int pos = line.from;
+ int end = 0;
+ QGlyphLayout glyphs;
+ const unsigned short *logClusters = eng->layoutData->logClustersPtr;
+ while (newItem < eng->layoutData->items.size()) {
+ if (newItem != item) {
+ item = newItem;
+ const QScriptItem &current = eng->layoutData->items[item];
+ if (!current.num_glyphs) {
+ eng->shape(item);
+ attributes = eng->attributes();
+ logClusters = eng->layoutData->logClustersPtr;
+ }
+ pos = qMax(line.from, current.position);
+ end = current.position + eng->length(item);
+ glyphs = eng->shapedGlyphs(&current);
+ }
+ const QScriptItem &current = eng->layoutData->items[item];
+
+ tmpData.ascent = qMax(tmpData.ascent, current.ascent);
+ tmpData.descent = qMax(tmpData.descent, current.descent);
+
+ if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) {
+ if (checkFullOtherwiseExtend(line, tmpData, spaceData, glyphCount, maxGlyphs, minw, manualWrap))
+ goto found;
+
+ QFixed x = line.x + line.textWidth + tmpData.textWidth + spaceData.textWidth;
+ spaceData.textWidth += eng->calculateTabWidth(item, x);
+ spaceData.length++;
+ newItem = item + 1;
+ ++glyphCount;
+ if (checkFullOtherwiseExtend(line, tmpData, spaceData, glyphCount, maxGlyphs, minw, manualWrap))
+ goto found;
+ } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) {
+ // if the line consists only of the line separator make sure
+ // we have a sane height
+ if (!line.length && !tmpData.length)
+ line.setDefaultHeight(eng);
+ if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) {
+ addNextCluster(pos, end, tmpData, glyphCount, current, logClusters, glyphs);
+ } else {
+ tmpData.length++;
+ }
+ line += tmpData;
+ goto found;
+ } else if (current.analysis.flags == QScriptAnalysis::Object) {
+ tmpData.length++;
+
+ QTextFormat format = eng->formats()->format(eng->formatIndex(&eng->layoutData->items[item]));
+ if (eng->block.docHandle())
+ eng->docLayout()->positionInlineObject(QTextInlineObject(item, eng), eng->block.position() + current.position, format);
+
+ tmpData.textWidth += current.width;
+
+ newItem = item + 1;
+ ++glyphCount;
+ if (checkFullOtherwiseExtend(line, tmpData, spaceData, glyphCount, maxGlyphs, minw, manualWrap))
+ goto found;
+ } else if (attributes[pos].whiteSpace) {
+ while (pos < end && attributes[pos].whiteSpace)
+ addNextCluster(pos, end, spaceData, glyphCount, current, logClusters, glyphs);
+
+ if (!manualWrap && spaceData.textWidth > line.width) {
+ spaceData.textWidth = line.width; // ignore spaces that fall out of the line.
+ goto found;
+ }
+ } else {
+ bool sb_or_ws = false;
+ do {
+ addNextCluster(pos, end, tmpData, glyphCount, current, logClusters, glyphs);
+
+ if (attributes[pos].whiteSpace || attributes[pos-1].lineBreakType != HB_NoBreak) {
+ sb_or_ws = true;
+ break;
+ } else if (breakany && attributes[pos].charStop) {
+ break;
+ }
+ } while (pos < end);
+ minw = qMax(tmpData.textWidth, minw);
+
+ QFixed softHyphenWidth;
+ if (pos && attributes[pos - 1].lineBreakType == HB_SoftHyphen) {
+ // if we are splitting up a word because of
+ // a soft hyphen then we ...
+ //
+ // a) have to take the width of the soft hyphen into
+ // account to see if the first syllable(s) /and/
+ // the soft hyphen fit into the line
+ //
+ // b) if we are so short of available width that the
+ // soft hyphen is the first breakable position, then
+ // we don't want to show it. However we initially
+ // have to take the width for it into accoun so that
+ // the text document layout sees the overflow and
+ // switch to break-anywhere mode, in which we
+ // want the soft-hyphen to slip into the next line
+ // and thus become invisible again.
+ //
+ if (line.length)
+ softHyphenWidth = glyphs.advances_x[logClusters[pos - 1]];
+ else if (breakany)
+ tmpData.textWidth += glyphs.advances_x[logClusters[pos - 1]];
+ }
+
+ if ((sb_or_ws|breakany)
+ && checkFullOtherwiseExtend(line, tmpData, spaceData, glyphCount, maxGlyphs, minw, manualWrap, softHyphenWidth)) {
+ if (!breakany) {
+ line.textWidth += softHyphenWidth;
+ }
+ goto found;
+ }
+ }
+ if (pos == end)
+ newItem = item + 1;
+ }
+ LB_DEBUG("reached end of line");
+ checkFullOtherwiseExtend(line, tmpData, spaceData, glyphCount, maxGlyphs, minw, manualWrap);
+found:
+ if (line.length == 0) {
+ LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f",
+ tmpData.length, tmpData.textWidth.toReal(), spaceData.length, spaceData.textWidth.toReal());
+ line += tmpData;
+ }
+
+ LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(),
+ line.descent.toReal(), line.textWidth.toReal(), spaceData.width.toReal());
+ LB_DEBUG(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data());
+
+ if (manualWrap) {
+ eng->minWidth = qMax(eng->minWidth, line.textWidth);
+ eng->maxWidth = qMax(eng->maxWidth, line.textWidth);
+ } else {
+ eng->minWidth = qMax(eng->minWidth, minw);
+ eng->maxWidth += line.textWidth;
+ }
+
+ if (line.textWidth > 0 && item < eng->layoutData->items.size())
+ eng->maxWidth += spaceData.textWidth;
+ if (eng->option.flags() & QTextOption::IncludeTrailingSpaces)
+ line.textWidth += spaceData.textWidth;
+ line.length += spaceData.length;
+ if (spaceData.length)
+ line.hasTrailingSpaces = true;
+
+ line.justified = false;
+ line.gridfitted = false;
+
+ if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) {
+ if ((maxGlyphs != INT_MAX && glyphCount > maxGlyphs)
+ || (maxGlyphs == INT_MAX && line.textWidth > line.width)) {
+
+ eng->option.setWrapMode(QTextOption::WrapAnywhere);
+ line.length = 0;
+ line.textWidth = 0;
+ layout_helper(maxGlyphs);
+ eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
+ }
+ }
+}
+
+/*!
+ Moves the line to position \a pos.
+*/
+void QTextLine::setPosition(const QPointF &pos)
+{
+ eng->lines[i].x = QFixed::fromReal(pos.x());
+ eng->lines[i].y = QFixed::fromReal(pos.y());
+}
+
+/*!
+ Returns the line's position relative to the text layout's position.
+*/
+QPointF QTextLine::position() const
+{
+ return QPointF(eng->lines[i].x.toReal(), eng->lines[i].y.toReal());
+}
+
+// ### DOC: I have no idea what this means/does.
+// You create a text layout with a string of text. Once you laid
+// it out, it contains a number of QTextLines. from() returns the position
+// inside the text string where this line starts. If you e.g. has a
+// text of "This is a string", laid out into two lines (the second
+// starting at the word 'a'), layout.lineAt(0).from() == 0 and
+// layout.lineAt(1).from() == 8.
+/*!
+ Returns the start of the line from the beginning of the string
+ passed to the QTextLayout.
+*/
+int QTextLine::textStart() const
+{
+ return eng->lines[i].from;
+}
+
+/*!
+ Returns the length of the text in the line.
+
+ \sa naturalTextWidth()
+*/
+int QTextLine::textLength() const
+{
+ if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators
+ && eng->block.isValid() && i == eng->lines.count()-1) {
+ return eng->lines[i].length - 1;
+ }
+ return eng->lines[i].length;
+}
+
+static void drawMenuText(QPainter *p, QFixed x, QFixed y, const QScriptItem &si, QTextItemInt &gf, QTextEngine *eng,
+ int start, int glyph_start)
+{
+ int ge = glyph_start + gf.glyphs.numGlyphs;
+ int gs = glyph_start;
+ int end = start + gf.num_chars;
+ unsigned short *logClusters = eng->logClusters(&si);
+ QGlyphLayout glyphs = eng->shapedGlyphs(&si);
+ QFixed orig_width = gf.width;
+
+ int *ul = eng->underlinePositions;
+ if (ul)
+ while (*ul != -1 && *ul < start)
+ ++ul;
+ bool rtl = si.analysis.bidiLevel % 2;
+ if (rtl)
+ x += si.width;
+
+ do {
+ int gtmp = ge;
+ int stmp = end;
+ if (ul && *ul != -1 && *ul < end) {
+ stmp = *ul;
+ gtmp = logClusters[*ul-si.position];
+ }
+
+ gf.glyphs = glyphs.mid(gs, gtmp - gs);
+ gf.num_chars = stmp - start;
+ gf.chars = eng->layoutData->string.unicode() + start;
+ QFixed w = 0;
+ while (gs < gtmp) {
+ w += glyphs.effectiveAdvance(gs);
+ ++gs;
+ }
+ start = stmp;
+ gf.width = w;
+ if (rtl)
+ x -= w;
+ if (gf.num_chars)
+ p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
+ if (!rtl)
+ x += w;
+ if (ul && *ul != -1 && *ul < end) {
+ // draw underline
+ gtmp = (*ul == end-1) ? ge : logClusters[*ul+1-si.position];
+ ++stmp;
+ gf.glyphs = glyphs.mid(gs, gtmp - gs);
+ gf.num_chars = stmp - start;
+ gf.chars = eng->layoutData->string.unicode() + start;
+ gf.logClusters = logClusters + start - si.position;
+ w = 0;
+ while (gs < gtmp) {
+ w += glyphs.effectiveAdvance(gs);
+ ++gs;
+ }
+ ++start;
+ gf.width = w;
+ gf.underlineStyle = QTextCharFormat::SingleUnderline;
+ if (rtl)
+ x -= w;
+ p->drawTextItem(QPointF(x.toReal(), y.toReal()), gf);
+ if (!rtl)
+ x += w;
+ gf.underlineStyle = QTextCharFormat::NoUnderline;
+ ++gf.chars;
+ ++ul;
+ }
+ } while (gs < ge);
+
+ gf.width = orig_width;
+}
+
+
+static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
+{
+ QBrush c = chf.foreground();
+ if (c.style() == Qt::NoBrush)
+ p->setPen(defaultPen);
+
+ QBrush bg = chf.background();
+ if (bg.style() != Qt::NoBrush)
+ p->fillRect(r, bg);
+ if (c.style() != Qt::NoBrush)
+ p->setPen(QPen(c, 0));
+}
+
+/*!
+ \fn void QTextLine::draw(QPainter *painter, const QPointF &position, const QTextLayout::FormatRange *selection) const
+
+ Draws a line on the given \a painter at the specified \a position.
+ The \a selection is reserved for internal use.
+*/
+void QTextLine::draw(QPainter *p, const QPointF &pos, const QTextLayout::FormatRange *selection) const
+{
+ const QScriptLine &line = eng->lines[i];
+ QPen pen = p->pen();
+
+ bool noText = (selection && selection->format.foreground().style() == Qt::NoBrush);
+
+ if (!line.length) {
+ if (selection
+ && selection->start <= line.from
+ && selection->start + selection->length > line.from) {
+
+ const qreal lineHeight = line.height().toReal();
+ QRectF r(pos.x() + line.x.toReal(), pos.y() + line.y.toReal(),
+ lineHeight / 2, QFontMetrics(eng->font()).width(QLatin1Char(' ')));
+ setPenAndDrawBackground(p, QPen(), selection->format, r);
+ p->setPen(pen);
+ }
+ return;
+ }
+
+
+ QTextLineItemIterator iterator(eng, i, pos, selection);
+ const QFixed y = QFixed::fromReal(pos.y()) + line.y + line.ascent;
+
+ bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
+ while (!iterator.atEnd()) {
+ QScriptItem &si = iterator.next();
+
+ if (selection && selection->start >= 0 && iterator.isOutsideSelection())
+ continue;
+
+ if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
+ && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
+ continue;
+
+ QFixed itemBaseLine = y;
+ QFont f = eng->font(si);
+ QTextCharFormat format;
+
+ if (eng->hasFormats() || selection) {
+ if (!suppressColors)
+ format = eng->format(&si);
+ if (selection)
+ format.merge(selection->format);
+
+ setPenAndDrawBackground(p, pen, format, QRectF(iterator.x.toReal(), (y - line.ascent).toReal(),
+ iterator.itemWidth.toReal(), line.height().toReal()));
+
+ QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
+ if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) {
+ QFontEngine *fe = f.d->engineForScript(si.analysis.script);
+ QFixed height = fe->ascent() + fe->descent();
+ if (valign == QTextCharFormat::AlignSubScript)
+ itemBaseLine += height / 6;
+ else if (valign == QTextCharFormat::AlignSuperScript)
+ itemBaseLine -= height / 2;
+ }
+ }
+
+ if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
+
+ if (eng->hasFormats()) {
+ p->save();
+ if (si.analysis.flags == QScriptAnalysis::Object && eng->block.docHandle()) {
+ QFixed itemY = y - si.ascent;
+ if (format.verticalAlignment() == QTextCharFormat::AlignTop) {
+ itemY = y - line.ascent;
+ }
+
+ QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
+
+ eng->docLayout()->drawInlineObject(p, itemRect,
+ QTextInlineObject(iterator.item, eng),
+ si.position + eng->block.position(),
+ format);
+ if (selection) {
+ QBrush bg = format.brushProperty(ObjectSelectionBrush);
+ if (bg.style() != Qt::NoBrush) {
+ QColor c = bg.color();
+ c.setAlpha(128);
+ p->fillRect(itemRect, c);
+ }
+ }
+ } else { // si.isTab
+ QFont f = eng->font(si);
+ QTextItemInt gf(si, &f, format);
+ gf.chars = 0;
+ gf.num_chars = 0;
+ gf.width = iterator.itemWidth;
+ p->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf);
+ if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
+ QChar visualTab(0x2192);
+ int w = QFontMetrics(f).width(visualTab);
+ qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
+ if (x < 0)
+ p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
+ iterator.itemWidth.toReal(), line.height().toReal()),
+ Qt::IntersectClip);
+ else
+ x /= 2; // Centered
+ p->drawText(QPointF(iterator.x.toReal() + x,
+ y.toReal()), visualTab);
+ }
+
+ }
+ p->restore();
+ }
+
+ continue;
+ }
+
+ unsigned short *logClusters = eng->logClusters(&si);
+ QGlyphLayout glyphs = eng->shapedGlyphs(&si);
+
+ QTextItemInt gf(si, &f, format);
+ gf.glyphs = glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart);
+ gf.chars = eng->layoutData->string.unicode() + iterator.itemStart;
+ gf.logClusters = logClusters + iterator.itemStart - si.position;
+ gf.num_chars = iterator.itemEnd - iterator.itemStart;
+ gf.width = iterator.itemWidth;
+ gf.justified = line.justified;
+
+ Q_ASSERT(gf.fontEngine);
+
+ if (eng->underlinePositions) {
+ // can't have selections in this case
+ drawMenuText(p, iterator.x, itemBaseLine, si, gf, eng, iterator.itemStart, iterator.glyphsStart);
+ } else {
+ QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
+ if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
+ QPainterPath path;
+ path.setFillRule(Qt::WindingFill);
+
+ if (gf.glyphs.numGlyphs)
+ gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
+ if (gf.flags) {
+ const QFontEngine *fe = gf.fontEngine;
+ const qreal lw = fe->lineThickness().toReal();
+ if (gf.flags & QTextItem::Underline) {
+ qreal offs = fe->underlinePosition().toReal();
+ path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
+ }
+ if (gf.flags & QTextItem::Overline) {
+ qreal offs = fe->ascent().toReal() + 1;
+ path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
+ }
+ if (gf.flags & QTextItem::StrikeOut) {
+ qreal offs = fe->ascent().toReal() / 3;
+ path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
+ }
+ }
+
+ p->save();
+ p->setRenderHint(QPainter::Antialiasing);
+ //Currently QPen with a Qt::NoPen style still returns a default
+ //QBrush which != Qt::NoBrush so we need this specialcase to reset it
+ if (p->pen().style() == Qt::NoPen)
+ p->setBrush(Qt::NoBrush);
+ else
+ p->setBrush(p->pen().brush());
+
+ p->setPen(format.textOutline());
+ p->drawPath(path);
+ p->restore();
+ } else {
+ if (noText)
+ gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
+ p->drawTextItem(pos, gf);
+ }
+ }
+ if (si.analysis.flags == QScriptAnalysis::Space
+ && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
+ QBrush c = format.foreground();
+ if (c.style() != Qt::NoBrush)
+ p->setPen(c.color());
+ QChar visualSpace((ushort)0xb7);
+ p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
+ p->setPen(pen);
+ }
+ }
+
+
+ if (eng->hasFormats())
+ p->setPen(pen);
+}
+
+/*!
+ \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const
+
+ \overload
+*/
+
+
+/*!
+ Converts the cursor position \a cursorPos to the corresponding x position
+ inside the line, taking account of the \a edge.
+
+ If \a cursorPos is not a valid cursor position, the nearest valid
+ cursor position will be used instead, and cpos will be modified to
+ point to this valid cursor position.
+
+ \sa xToCursor()
+*/
+qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const
+{
+ if (!eng->layoutData)
+ eng->itemize();
+
+ const QScriptLine &line = eng->lines[i];
+
+ QFixed x = line.x;
+ x += alignLine(eng, line);
+
+ if (!i && !eng->layoutData->items.size()) {
+ *cursorPos = 0;
+ return x.toReal();
+ }
+
+ int pos = *cursorPos;
+ int itm;
+ if (pos == line.from + (int)line.length) {
+ // end of line ensure we have the last item on the line
+ itm = eng->findItem(pos-1);
+ }
+ else
+ itm = eng->findItem(pos);
+ eng->shapeLine(line);
+
+ const QScriptItem *si = &eng->layoutData->items[itm];
+ if (!si->num_glyphs)
+ eng->shape(itm);
+ pos -= si->position;
+
+ QGlyphLayout glyphs = eng->shapedGlyphs(si);
+ unsigned short *logClusters = eng->logClusters(si);
+ Q_ASSERT(logClusters);
+
+ int l = eng->length(itm);
+ if (pos > l)
+ pos = l;
+ if (pos < 0)
+ pos = 0;
+
+ int glyph_pos = pos == l ? si->num_glyphs : logClusters[pos];
+ if (edge == Trailing) {
+ // trailing edge is leading edge of next cluster
+ while (glyph_pos < si->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart)
+ glyph_pos++;
+ }
+
+ bool reverse = eng->layoutData->items[itm].analysis.bidiLevel % 2;
+
+ int lineEnd = line.from + line.length;
+
+ // add the items left of the cursor
+
+ int firstItem = eng->findItem(line.from);
+ int lastItem = eng->findItem(lineEnd - 1);
+ int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
+
+ QVarLengthArray<int> visualOrder(nItems);
+ QVarLengthArray<uchar> levels(nItems);
+ for (int i = 0; i < nItems; ++i)
+ levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
+ QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
+
+ for (int i = 0; i < nItems; ++i) {
+ int item = visualOrder[i]+firstItem;
+ if (item == itm)
+ break;
+ QScriptItem &si = eng->layoutData->items[item];
+ if (!si.num_glyphs)
+ eng->shape(item);
+
+ if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
+ x += si.width;
+ continue;
+ }
+ int start = qMax(line.from, si.position);
+ int end = qMin(lineEnd, si.position + eng->length(item));
+
+ logClusters = eng->logClusters(&si);
+
+ int gs = logClusters[start-si.position];
+ int ge = (end == si.position + eng->length(item)) ? si.num_glyphs-1 : logClusters[end-si.position-1];
+
+ QGlyphLayout glyphs = eng->shapedGlyphs(&si);
+
+ while (gs <= ge) {
+ x += glyphs.effectiveAdvance(gs);
+ ++gs;
+ }
+ }
+
+ logClusters = eng->logClusters(si);
+ glyphs = eng->shapedGlyphs(si);
+ if (si->analysis.flags >= QScriptAnalysis::TabOrObject) {
+ if(pos == l)
+ x += si->width;
+ } else {
+ int offsetInCluster = 0;
+ for (int i=pos-1; i >= 0; i--) {
+ if (logClusters[i] == glyph_pos)
+ offsetInCluster++;
+ else
+ break;
+ }
+
+ if (reverse) {
+ int end = qMin(lineEnd, si->position + l) - si->position;
+ int glyph_end = end == l ? si->num_glyphs : logClusters[end];
+ for (int i = glyph_end - 1; i >= glyph_pos; i--)
+ x += glyphs.effectiveAdvance(i);
+ } else {
+ int start = qMax(line.from - si->position, 0);
+ int glyph_start = logClusters[start];
+ for (int i = glyph_start; i < glyph_pos; i++)
+ x += glyphs.effectiveAdvance(i);
+ }
+ if (offsetInCluster > 0) { // in the case that the offset is inside a (multi-character) glyph, interpolate the position.
+ int clusterLength = 0;
+ for (int i=pos - offsetInCluster; i < line.length; i++) {
+ if (logClusters[i] == glyph_pos)
+ clusterLength++;
+ else
+ break;
+ }
+ if (clusterLength)
+ x+= glyphs.advances_x[glyph_pos] * offsetInCluster / clusterLength;
+ }
+ }
+
+ *cursorPos = pos + si->position;
+ return x.toReal();
+}
+
+/*!
+ \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const
+
+ Converts the x-coordinate \a x, to the nearest matching cursor
+ position, depending on the cursor position type, \a cpos.
+
+ \sa cursorToX()
+*/
+int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const
+{
+ QFixed x = QFixed::fromReal(_x);
+ const QScriptLine &line = eng->lines[i];
+
+ if (!eng->layoutData)
+ eng->itemize();
+
+ int line_length = textLength();
+
+ if (!line_length)
+ return line.from;
+
+ int firstItem = eng->findItem(line.from);
+ int lastItem = eng->findItem(line.from + line_length - 1);
+ int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0;
+
+ if (!nItems)
+ return 0;
+
+ x -= line.x;
+ x -= alignLine(eng, line);
+// qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos);
+
+ QVarLengthArray<int> visualOrder(nItems);
+ QVarLengthArray<unsigned char> levels(nItems);
+ for (int i = 0; i < nItems; ++i)
+ levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel;
+ QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data());
+
+ if (x <= 0) {
+ // left of first item
+ int item = visualOrder[0]+firstItem;
+ QScriptItem &si = eng->layoutData->items[item];
+ if (!si.num_glyphs)
+ eng->shape(item);
+ int pos = si.position;
+ if (si.analysis.bidiLevel % 2)
+ pos += eng->length(item);
+ pos = qMax(line.from, pos);
+ pos = qMin(line.from + line_length, pos);
+ return pos;
+ } else if (x < line.textWidth
+ || (line.justified && x < line.width)) {
+ // has to be in one of the runs
+ QFixed pos;
+
+ eng->shapeLine(line);
+ for (int i = 0; i < nItems; ++i) {
+ int item = visualOrder[i]+firstItem;
+ QScriptItem &si = eng->layoutData->items[item];
+ int item_length = eng->length(item);
+// qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal());
+
+ int start = qMax(line.from - si.position, 0);
+ int end = qMin(line.from + line_length - si.position, item_length);
+
+ unsigned short *logClusters = eng->logClusters(&si);
+
+ int gs = logClusters[start];
+ int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1;
+ QGlyphLayout glyphs = eng->shapedGlyphs(&si);
+
+ QFixed item_width = 0;
+ if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
+ item_width = si.width;
+ } else {
+ int g = gs;
+ while (g <= ge) {
+ item_width += glyphs.effectiveAdvance(g);
+ ++g;
+ }
+ }
+// qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal());
+
+ if (pos + item_width < x) {
+ pos += item_width;
+ continue;
+ }
+// qDebug(" inside run");
+ if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
+ if (cpos == QTextLine::CursorOnCharacter)
+ return si.position;
+ bool left_half = (x - pos) < item_width/2;
+
+ if (bool(si.analysis.bidiLevel % 2) != left_half)
+ return si.position;
+ return si.position + 1;
+ }
+
+ int glyph_pos = -1;
+ // has to be inside run
+ if (cpos == QTextLine::CursorOnCharacter) {
+ if (si.analysis.bidiLevel % 2) {
+ pos += item_width;
+ glyph_pos = gs;
+ while (gs <= ge) {
+ if (glyphs.attributes[gs].clusterStart) {
+ if (pos < x)
+ break;
+ glyph_pos = gs;
+ break;
+ }
+ pos -= glyphs.effectiveAdvance(gs);
+ ++gs;
+ }
+ } else {
+ glyph_pos = gs;
+ while (gs <= ge) {
+ if (glyphs.attributes[gs].clusterStart) {
+ if (pos > x)
+ break;
+ glyph_pos = gs;
+ }
+ pos += glyphs.effectiveAdvance(gs);
+ ++gs;
+ }
+ }
+ } else {
+ QFixed dist = INT_MAX/256;
+ if (si.analysis.bidiLevel % 2) {
+ pos += item_width;
+ while (gs <= ge) {
+ if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
+ glyph_pos = gs;
+ dist = qAbs(x-pos);
+ }
+ pos -= glyphs.effectiveAdvance(gs);
+ ++gs;
+ }
+ } else {
+ while (gs <= ge) {
+ if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) {
+ glyph_pos = gs;
+ dist = qAbs(x-pos);
+ }
+ pos += glyphs.effectiveAdvance(gs);
+ ++gs;
+ }
+ }
+ if (qAbs(x-pos) < dist)
+ return si.position + end;
+ }
+ Q_ASSERT(glyph_pos != -1);
+ int j;
+ for (j = 0; j < eng->length(item); ++j)
+ if (logClusters[j] == glyph_pos)
+ break;
+// qDebug("at pos %d (in run: %d)", si.position + j, j);
+ return si.position + j;
+ }
+ }
+ // right of last item
+// qDebug() << "right of last";
+ int item = visualOrder[nItems-1]+firstItem;
+ QScriptItem &si = eng->layoutData->items[item];
+ if (!si.num_glyphs)
+ eng->shape(item);
+ int pos = si.position;
+ if (!(si.analysis.bidiLevel % 2))
+ pos += eng->length(item);
+ pos = qMax(line.from, pos);
+
+ int maxPos = line.from + line_length;
+
+ // except for the last line we assume that the
+ // character between lines is a space and we want
+ // to position the cursor to the left of that
+ // character.
+ // ###### breaks with japanese for example
+ if (this->i < eng->lines.count() - 1)
+ --maxPos;
+
+ pos = qMin(pos, maxPos);
+ return pos;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/text/qtextlayout.h b/src/gui/text/qtextlayout.h
new file mode 100644
index 0000000000..e5acb8ea03
--- /dev/null
+++ b/src/gui/text/qtextlayout.h
@@ -0,0 +1,243 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef QTEXTLAYOUT_H
+#define QTEXTLAYOUT_H
+
+#include <QtCore/qstring.h>
+#include <QtCore/qnamespace.h>
+#include <QtCore/qrect.h>
+#include <QtCore/qvector.h>
+#include <QtGui/qcolor.h>
+#include <QtCore/qobject.h>
+#include <QtGui/qevent.h>
+#include <QtGui/qtextformat.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Gui)
+
+class QTextEngine;
+class QFont;
+class QRect;
+class QRegion;
+class QTextFormat;
+class QPalette;
+class QPainter;
+
+class Q_GUI_EXPORT QTextInlineObject
+{
+public:
+ QTextInlineObject(int i, QTextEngine *e) : itm(i), eng(e) {}
+ inline QTextInlineObject() : itm(0), eng(0) {}
+ inline bool isValid() const { return eng; }
+
+ QRectF rect() const;
+ qreal width() const;
+ qreal ascent() const;
+ qreal descent() const;
+ qreal height() const;
+
+ Qt::LayoutDirection textDirection() const;
+
+ void setWidth(qreal w);
+ void setAscent(qreal a);
+ void setDescent(qreal d);
+
+ int textPosition() const;
+
+ int formatIndex() const;
+ QTextFormat format() const;
+
+private:
+ friend class QTextLayout;
+ int itm;
+ QTextEngine *eng;
+};
+
+class QPaintDevice;
+class QTextFormat;
+class QTextLine;
+class QTextBlock;
+class QTextOption;
+
+class Q_GUI_EXPORT QTextLayout
+{
+public:
+ // does itemization
+ QTextLayout();
+ QTextLayout(const QString& text);
+ QTextLayout(const QString& text, const QFont &font, QPaintDevice *paintdevice = 0);
+ QTextLayout(const QTextBlock &b);
+ ~QTextLayout();
+
+ void setFont(const QFont &f);
+ QFont font() const;
+
+ void setText(const QString& string);
+ QString text() const;
+
+ void setTextOption(const QTextOption &option);
+ QTextOption textOption() const;
+
+ void setPreeditArea(int position, const QString &text);
+ int preeditAreaPosition() const;
+ QString preeditAreaText() const;
+
+ struct FormatRange {
+ int start;
+ int length;
+ QTextCharFormat format;
+ };
+ void setAdditionalFormats(const QList<FormatRange> &overrides);
+ QList<FormatRange> additionalFormats() const;
+ void clearAdditionalFormats();
+
+ void setCacheEnabled(bool enable);
+ bool cacheEnabled() const;
+
+ void beginLayout();
+ void endLayout();
+ void clearLayout();
+
+ QTextLine createLine();
+
+ int lineCount() const;
+ QTextLine lineAt(int i) const;
+ QTextLine lineForTextPosition(int pos) const;
+
+ enum CursorMode {
+ SkipCharacters,
+ SkipWords
+ };
+ bool isValidCursorPosition(int pos) const;
+ int nextCursorPosition(int oldPos, CursorMode mode = SkipCharacters) const;
+ int previousCursorPosition(int oldPos, CursorMode mode = SkipCharacters) const;
+
+ void draw(QPainter *p, const QPointF &pos, const QVector<FormatRange> &selections = QVector<FormatRange>(),
+ const QRectF &clip = QRectF()) const;
+ void drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const;
+ void drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const;
+
+ QPointF position() const;
+ void setPosition(const QPointF &p);
+
+ QRectF boundingRect() const;
+
+ qreal minimumWidth() const;
+ qreal maximumWidth() const;
+
+ QTextEngine *engine() const { return d; }
+ void setFlags(int flags);
+private:
+ QTextLayout(QTextEngine *e) : d(e) {}
+ Q_DISABLE_COPY(QTextLayout)
+
+ friend class QPainter;
+ friend class QPSPrinter;
+ friend class QGraphicsSimpleTextItemPrivate;
+ friend class QGraphicsSimpleTextItem;
+ friend void qt_format_text(const QFont &font, const QRectF &_r, int tf, const QTextOption *, const QString& str,
+ QRectF *brect, int tabstops, int* tabarray, int tabarraylen,
+ QPainter *painter);
+ QTextEngine *d;
+};
+
+
+class Q_GUI_EXPORT QTextLine
+{
+public:
+ inline QTextLine() : i(0), eng(0) {}
+ inline bool isValid() const { return eng; }
+
+ QRectF rect() const;
+ qreal x() const;
+ qreal y() const;
+ qreal width() const;
+ qreal ascent() const;
+ qreal descent() const;
+ qreal height() const;
+
+ qreal naturalTextWidth() const;
+ QRectF naturalTextRect() const;
+
+ enum Edge {
+ Leading,
+ Trailing
+ };
+ enum CursorPosition {
+ CursorBetweenCharacters,
+ CursorOnCharacter
+ };
+
+ /* cursorPos gets set to the valid position */
+ qreal cursorToX(int *cursorPos, Edge edge = Leading) const;
+ inline qreal cursorToX(int cursorPos, Edge edge = Leading) const { return cursorToX(&cursorPos, edge); }
+ int xToCursor(qreal x, CursorPosition = CursorBetweenCharacters) const;
+
+ void setLineWidth(qreal width);
+ void setNumColumns(int columns);
+ void setNumColumns(int columns, qreal alignmentWidth);
+
+ void setPosition(const QPointF &pos);
+ QPointF position() const;
+
+ int textStart() const;
+ int textLength() const;
+
+ int lineNumber() const { return i; }
+
+ void draw(QPainter *p, const QPointF &point, const QTextLayout::FormatRange *selection = 0) const;
+
+private:
+ QTextLine(int line, QTextEngine *e) : i(line), eng(e) {}
+ void layout_helper(int numGlyphs);
+ friend class QTextLayout;
+ int i;
+ QTextEngine *eng;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QTEXTLAYOUT_H
diff --git a/src/gui/text/qtextlist.cpp b/src/gui/text/qtextlist.cpp
new file mode 100644
index 0000000000..e305027efc
--- /dev/null
+++ b/src/gui/text/qtextlist.cpp
@@ -0,0 +1,261 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include "qtextlist.h"
+#include "qtextobject_p.h"
+#include "qtextcursor.h"
+#include "qtextdocument_p.h"
+#include <qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+class QTextListPrivate : public QTextBlockGroupPrivate
+{
+};
+
+/*!
+ \class QTextList
+ \reentrant
+
+ \brief The QTextList class provides a decorated list of items in a QTextDocument.
+
+ \ingroup text
+
+ A list contains a sequence of text blocks, each of which is marked with a
+ bullet point or other symbol. Multiple levels of lists can be used, and
+ the automatic numbering feature provides support for ordered numeric and
+ alphabetical lists.
+
+ Lists are created by using a text cursor to insert an empty list at the
+ current position or by moving existing text into a new list.
+ The \l{QTextCursor::insertList()} function inserts an empty block into the
+ document at the cursor position, and makes it the first item in a list.
+
+ \snippet doc/src/snippets/textdocument-lists/mainwindow.cpp 0
+
+ The \l{QTextCursor::createList()} function takes the contents of the
+ cursor's current block and turns it into the first item of a new list.
+
+ The cursor's current list is found with \l{QTextCursor::currentList()}.
+
+ The number of items in a list is given by count(). Each item can be
+ obtained by its index in the list with the item() function. Similarly,
+ the index of a given item can be found with itemNumber(). The text of
+ each item can be found with the itemText() function.
+
+ Note that the items in the list may not be adjacent elements in the
+ document. For example, the top-level items in a multi-level list will
+ be separated by the items in lower levels of the list.
+
+ List items can be deleted by index with the removeItem() function.
+ remove() deletes the specified item in the list.
+
+ The list's format is set with setFormat() and read with format().
+ The format describes the decoration of the list itself, and not the
+ individual items.
+
+ \sa QTextBlock, QTextListFormat, QTextCursor
+*/
+
+/*!
+ \fn bool QTextList::isEmpty() const
+ \obsolete
+
+ Returns true if the list has no items; otherwise returns false.
+
+ \bold{Note:} Empty lists are automatically deleted by the QTextDocument that owns
+ them.
+
+ \sa count()
+*/
+
+/*! \internal
+ */
+QTextList::QTextList(QTextDocument *doc)
+ : QTextBlockGroup(*new QTextListPrivate, doc)
+{
+}
+
+/*!
+ \internal
+*/
+QTextList::~QTextList()
+{
+}
+
+/*!
+ Returns the number of items in the list.
+
+ \sa isEmpty()
+*/
+int QTextList::count() const
+{
+ Q_D(const QTextList);
+ return d->blocks.count();
+}
+
+/*!
+ Returns the \a{i}-th text block in the list.
+
+ \sa count() itemText()
+*/
+QTextBlock QTextList::item(int i) const
+{
+ Q_D(const QTextList);
+ if (i < 0 || i >= d->blocks.size())
+ return QTextBlock();
+ return d->blocks.at(i);
+}
+
+/*!
+ \fn void QTextList::setFormat(const QTextListFormat &format)
+
+ Sets the list's format to \a format.
+*/
+
+/*!
+ \fn QTextListFormat QTextList::format() const
+
+ Returns the list's format.
+*/
+
+/*!
+ \fn int QTextList::itemNumber(const QTextBlock &block) const
+
+ Returns the index of the list item that corresponds to the given \a block.
+ Returns -1 if the block was not present in the list.
+*/
+int QTextList::itemNumber(const QTextBlock &blockIt) const
+{
+ Q_D(const QTextList);
+ return d->blocks.indexOf(blockIt);
+}
+
+/*!
+ \fn QString QTextList::itemText(const QTextBlock &block) const
+
+ Returns the text of the list item that corresponds to the given \a block.
+*/
+QString QTextList::itemText(const QTextBlock &blockIt) const
+{
+ Q_D(const QTextList);
+ int item = d->blocks.indexOf(blockIt) + 1;
+ if (item <= 0)
+ return QString();
+
+ QTextBlock block = d->blocks.at(item-1);
+ QTextBlockFormat blockFormat = block.blockFormat();
+
+ QString result;
+
+ const int style = format().style();
+
+ switch (style) {
+ case QTextListFormat::ListDecimal:
+ result = QString::number(item);
+ break;
+ // from the old richtext
+ case QTextListFormat::ListLowerAlpha:
+ case QTextListFormat::ListUpperAlpha:
+ {
+ const char baseChar = style == QTextListFormat::ListUpperAlpha ? 'A' : 'a';
+
+ int c = item;
+ while (c > 0) {
+ c--;
+ result.prepend(QChar(baseChar + (c % 26)));
+ c /= 26;
+ }
+ }
+ break;
+ default:
+ Q_ASSERT(false);
+ }
+ if (blockFormat.layoutDirection() == Qt::RightToLeft)
+ return result.prepend(QLatin1Char('.'));
+ return result + QLatin1Char('.');
+}
+
+/*!
+ Removes the item at item position \a i from the list. When the last item in the
+ list is removed, the list is automatically deleted by the QTextDocument that owns
+ it.
+
+ \sa add(), remove()
+*/
+void QTextList::removeItem(int i)
+{
+ Q_D(QTextList);
+ if (i < 0 || i >= d->blocks.size())
+ return;
+
+ QTextBlock block = d->blocks.at(i);
+ remove(block);
+}
+
+
+/*!
+ Removes the given \a block from the list.
+
+ \sa add(), removeItem()
+*/
+void QTextList::remove(const QTextBlock &block)
+{
+ QTextBlockFormat fmt = block.blockFormat();
+ fmt.setIndent(fmt.indent() + format().indent());
+ fmt.setObjectIndex(-1);
+ block.docHandle()->setBlockFormat(block, block, fmt, QTextDocumentPrivate::SetFormat);
+}
+
+/*!
+ Makes the given \a block part of the list.
+
+ \sa remove(), removeItem()
+*/
+void QTextList::add(const QTextBlock &block)
+{
+ QTextBlockFormat fmt = block.blockFormat();
+ fmt.setObjectIndex(objectIndex());
+ block.docHandle()->setBlockFormat(block, block, fmt, QTextDocumentPrivate::SetFormat);
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/text/qtextlist.h b/src/gui/text/qtextlist.h
new file mode 100644
index 0000000000..ab8d2d9d50
--- /dev/null
+++ b/src/gui/text/qtextlist.h
@@ -0,0 +1,94 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTLIST_H
+#define QTEXTLIST_H
+
+#include <QtGui/qtextobject.h>
+#include <QtCore/qobject.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Gui)
+
+class QTextListPrivate;
+class QTextCursor;
+
+class Q_GUI_EXPORT QTextList : public QTextBlockGroup
+{
+ Q_OBJECT
+public:
+ explicit QTextList(QTextDocument *doc);
+ ~QTextList();
+
+ int count() const;
+
+ inline bool isEmpty() const
+ { return count() == 0; }
+
+ QTextBlock item(int i) const;
+
+ int itemNumber(const QTextBlock &) const;
+ QString itemText(const QTextBlock &) const;
+
+ void removeItem(int i);
+ void remove(const QTextBlock &);
+
+ void add(const QTextBlock &block);
+
+ inline void setFormat(const QTextListFormat &format);
+ QTextListFormat format() const { return QTextObject::format().toListFormat(); }
+
+private:
+ Q_DISABLE_COPY(QTextList)
+ Q_DECLARE_PRIVATE(QTextList)
+};
+
+inline void QTextList::setFormat(const QTextListFormat &aformat)
+{ QTextObject::setFormat(aformat); }
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QTEXTLIST_H
diff --git a/src/gui/text/qtextobject.cpp b/src/gui/text/qtextobject.cpp
new file mode 100644
index 0000000000..1645a21cf4
--- /dev/null
+++ b/src/gui/text/qtextobject.cpp
@@ -0,0 +1,1711 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtextobject.h"
+#include "qtextobject_p.h"
+#include "qtextdocument.h"
+#include "qtextformat_p.h"
+#include "qtextdocument_p.h"
+#include "qtextcursor.h"
+#include "qtextlist.h"
+#include "qabstracttextdocumentlayout.h"
+#include "qtextengine_p.h"
+#include "qdebug.h"
+
+QT_BEGIN_NAMESPACE
+
+// ### DOC: We ought to explain the CONCEPT of objectIndexes if
+// relevant to the public API
+/*!
+ \class QTextObject
+ \reentrant
+
+ \brief The QTextObject class is a base class for different kinds
+ of objects that can group parts of a QTextDocument together.
+
+ \ingroup text
+
+ The common grouping text objects are lists (QTextList), frames
+ (QTextFrame), and tables (QTextTable). A text object has an
+ associated format() and document().
+
+ There are essentially two kinds of text objects: those that are used
+ with blocks (block formats), and those that are used with characters
+ (character formats). The first kind are derived from QTextBlockGroup,
+ and the second kind from QTextFrame.
+
+ You rarely need to use this class directly. When creating custom text
+ objects, you will also need to reimplement QTextDocument::createObject()
+ which acts as a factory method for creating text objects.
+
+ \sa QTextDocument
+*/
+
+/*!
+ \fn QTextObject::QTextObject(QTextDocument *document)
+
+ Creates a new QTextObject for the given \a document.
+
+ \warning This function should never be called directly, but only
+ from QTextDocument::createObject().
+*/
+QTextObject::QTextObject(QTextDocument *doc)
+ : QObject(*new QTextObjectPrivate, doc)
+{
+}
+
+/*!
+ \fn QTextObject::QTextObject(QTextObjectPrivate &p, QTextDocument *document)
+
+ \internal
+*/
+QTextObject::QTextObject(QTextObjectPrivate &p, QTextDocument *doc)
+ :QObject(p, doc)
+{
+}
+
+/*!
+ Destroys the text object.
+
+ \warning Text objects are owned by the document, so you should
+ never destroy them yourself.
+*/
+QTextObject::~QTextObject()
+{
+}
+
+/*!
+ Returns the text object's format.
+
+ \sa setFormat() document()
+*/
+QTextFormat QTextObject::format() const
+{
+ Q_D(const QTextObject);
+ return d->pieceTable->formatCollection()->objectFormat(d->objectIndex);
+}
+
+/*!
+ Returns the index of the object's format in the document's internal
+ list of formats.
+
+ \sa QTextDocument::allFormats()
+*/
+int QTextObject::formatIndex() const
+{
+ Q_D(const QTextObject);
+ return d->pieceTable->formatCollection()->objectFormatIndex(d->objectIndex);
+}
+
+
+/*!
+ Sets the text object's \a format.
+
+ \sa format()
+*/
+void QTextObject::setFormat(const QTextFormat &format)
+{
+ Q_D(QTextObject);
+ int idx = d->pieceTable->formatCollection()->indexForFormat(format);
+ d->pieceTable->changeObjectFormat(this, idx);
+}
+
+/*!
+ Returns the object index of this object. This can be used together with
+ QTextFormat::setObjectIndex().
+*/
+int QTextObject::objectIndex() const
+{
+ Q_D(const QTextObject);
+ return d->objectIndex;
+}
+
+/*!
+ Returns the document this object belongs to.
+
+ \sa format()
+*/
+QTextDocument *QTextObject::document() const
+{
+ return static_cast<QTextDocument *>(parent());
+}
+
+/*!
+ \internal
+*/
+QTextDocumentPrivate *QTextObject::docHandle() const
+{
+ return static_cast<const QTextDocument *>(parent())->docHandle();
+}
+
+/*!
+ \class QTextBlockGroup
+ \reentrant
+
+ \brief The QTextBlockGroup class provides a container for text blocks within
+ a QTextDocument.
+
+ \ingroup text
+
+ Block groups can be used to organize blocks of text within a document.
+ They maintain an up-to-date list of the text blocks that belong to
+ them, even when text blocks are being edited.
+
+ Each group has a parent document which is specified when the group is
+ constructed.
+
+ Text blocks can be inserted into a group with blockInserted(), and removed
+ with blockRemoved(). If a block's format is changed, blockFormatChanged()
+ is called.
+
+ The list of blocks in the group is returned by blockList(). Note that the
+ blocks in the list are not necessarily adjacent elements in the document;
+ for example, the top-level items in a multi-level list will be separated
+ by the items in lower levels of the list.
+
+ \sa QTextBlock QTextDocument
+*/
+
+void QTextBlockGroupPrivate::markBlocksDirty()
+{
+ for (int i = 0; i < blocks.count(); ++i) {
+ const QTextBlock &block = blocks.at(i);
+ pieceTable->documentChange(block.position(), block.length());
+ }
+}
+
+/*!
+ \fn QTextBlockGroup::QTextBlockGroup(QTextDocument *document)
+
+ Creates a new new block group for the given \a document.
+
+ \warning This function should only be called from
+ QTextDocument::createObject().
+*/
+QTextBlockGroup::QTextBlockGroup(QTextDocument *doc)
+ : QTextObject(*new QTextBlockGroupPrivate, doc)
+{
+}
+
+/*!
+ \internal
+*/
+QTextBlockGroup::QTextBlockGroup(QTextBlockGroupPrivate &p, QTextDocument *doc)
+ : QTextObject(p, doc)
+{
+}
+
+/*!
+ Destroys this block group; the blocks are not deleted, they simply
+ don't belong to this block anymore.
+*/
+QTextBlockGroup::~QTextBlockGroup()
+{
+}
+
+// ### DOC: Shouldn't this be insertBlock()?
+/*!
+ Appends the given \a block to the end of the group.
+
+ \warning If you reimplement this function you must call the base
+ class implementation.
+*/
+void QTextBlockGroup::blockInserted(const QTextBlock &block)
+{
+ Q_D(QTextBlockGroup);
+ QTextBlockGroupPrivate::BlockList::Iterator it = qLowerBound(d->blocks.begin(), d->blocks.end(), block);
+ d->blocks.insert(it, block);
+ d->markBlocksDirty();
+}
+
+// ### DOC: Shouldn't this be removeBlock()?
+/*!
+ Removes the given \a block from the group; the block itself is not
+ deleted, it simply isn't a member of this group anymore.
+*/
+void QTextBlockGroup::blockRemoved(const QTextBlock &block)
+{
+ Q_D(QTextBlockGroup);
+ d->blocks.removeAll(block);
+ d->markBlocksDirty();
+ if (d->blocks.isEmpty()) {
+ document()->docHandle()->deleteObject(this);
+ return;
+ }
+}
+
+/*!
+ This function is called whenever the specified \a block of text is changed.
+ The text block is a member of this group.
+
+ The base class implementation does nothing.
+*/
+void QTextBlockGroup::blockFormatChanged(const QTextBlock &)
+{
+}
+
+/*!
+ Returns a (possibly empty) list of all the blocks that are part of
+ the block group.
+*/
+QList<QTextBlock> QTextBlockGroup::blockList() const
+{
+ Q_D(const QTextBlockGroup);
+ return d->blocks;
+}
+
+
+
+QTextFrameLayoutData::~QTextFrameLayoutData()
+{
+}
+
+
+/*!
+ \class QTextFrame
+ \reentrant
+
+ \brief The QTextFrame class represents a frame in a QTextDocument.
+
+ \ingroup text
+
+ Text frames provide structure for the text in a document. They are used
+ as generic containers for other document elements.
+ Frames are usually created by using QTextCursor::insertFrame().
+
+ \omit
+ Each frame in a document consists of a frame start character,
+ QChar(0xFDD0), followed by the frame's contents, followed by a
+ frame end character, QChar(0xFDD1). The character formats of the
+ start and end character contain a reference to the frame object's
+ objectIndex.
+ \endomit
+
+ Frames can be used to create hierarchical structures in rich text documents.
+ Each document has a root frame (QTextDocument::rootFrame()), and each frame
+ beneath the root frame has a parent frame and a (possibly empty) list of
+ child frames. The parent frame can be found with parentFrame(), and the
+ childFrames() function provides a list of child frames.
+
+ Each frame contains at least one text block to enable text cursors to
+ insert new document elements within. As a result, the QTextFrame::iterator
+ class is used to traverse both the blocks and child frames within a given
+ frame. The first and last child elements in the frame can be found with
+ begin() and end().
+
+ A frame also has a format (specified using QTextFrameFormat) which can be set
+ with setFormat() and read with format().
+
+ Text cursors can be obtained that point to the first and last valid cursor
+ positions within a frame; use the firstCursorPosition() and
+ lastCursorPosition() functions for this. The frame's extent in the
+ document can be found with firstPosition() and lastPosition().
+
+ You can iterate over a frame's contents using the
+ QTextFrame::iterator class: this provides read-only access to its
+ internal list of text blocks and child frames.
+
+ \sa QTextCursor QTextDocument
+*/
+
+/*!
+ \typedef QTextFrame::Iterator
+
+ Qt-style synonym for QTextFrame::iterator.
+*/
+
+/*!
+ \fn QTextFrame *QTextFrame::iterator::parentFrame() const
+
+ Returns the parent frame of the current frame.
+
+ \sa currentFrame() QTextFrame::parentFrame()
+*/
+
+/*!
+ \fn bool QTextFrame::iterator::operator==(const iterator &other) const
+
+ Retuns true if the iterator is the same as the \a other iterator;
+ otherwise returns false.
+*/
+
+/*!
+ \fn bool QTextFrame::iterator::operator!=(const iterator &other) const
+
+ Retuns true if the iterator is different from the \a other iterator;
+ otherwise returns false.
+*/
+
+/*!
+ \fn QTextFrame::iterator QTextFrame::iterator::operator++(int)
+
+ The postfix ++ operator (\c{i++}) advances the iterator to the
+ next item in the text frame, and returns an iterator to the old item.
+*/
+
+/*!
+ \fn QTextFrame::iterator QTextFrame::iterator::operator--(int)
+
+ The postfix -- operator (\c{i--}) makes the preceding item in the
+ current frame, and returns an iterator to the old item.
+*/
+
+/*!
+ \fn void QTextFrame::setFrameFormat(const QTextFrameFormat &format)
+
+ Sets the frame's \a format.
+
+ \sa frameFormat()
+*/
+
+/*!
+ \fn QTextFrameFormat QTextFrame::frameFormat() const
+
+ Returns the frame's format.
+
+ \sa setFrameFormat()
+*/
+
+/*!
+ \fn QTextFrame::QTextFrame(QTextDocument *document)
+
+ Creates a new empty frame for the text \a document.
+*/
+QTextFrame::QTextFrame(QTextDocument *doc)
+ : QTextObject(*new QTextFramePrivate, doc)
+{
+ Q_D(QTextFrame);
+ d->fragment_start = 0;
+ d->fragment_end = 0;
+ d->parentFrame = 0;
+ d->layoutData = 0;
+}
+
+// ### DOC: What does this do to child frames?
+/*!
+ Destroys the frame, and removes it from the document's layout.
+*/
+QTextFrame::~QTextFrame()
+{
+ Q_D(QTextFrame);
+ delete d->layoutData;
+}
+
+/*!
+ \internal
+*/
+QTextFrame::QTextFrame(QTextFramePrivate &p, QTextDocument *doc)
+ : QTextObject(p, doc)
+{
+ Q_D(QTextFrame);
+ d->fragment_start = 0;
+ d->fragment_end = 0;
+ d->parentFrame = 0;
+ d->layoutData = 0;
+}
+
+/*!
+ Returns a (possibly empty) list of the frame's child frames.
+
+ \sa parentFrame()
+*/
+QList<QTextFrame *> QTextFrame::childFrames() const
+{
+ Q_D(const QTextFrame);
+ return d->childFrames;
+}
+
+/*!
+ Returns the frame's parent frame. If the frame is the root frame of a
+ document, this will return 0.
+
+ \sa childFrames() QTextDocument::rootFrame()
+*/
+QTextFrame *QTextFrame::parentFrame() const
+{
+ Q_D(const QTextFrame);
+ return d->parentFrame;
+}
+
+
+/*!
+ Returns the first cursor position inside the frame.
+
+ \sa lastCursorPosition() firstPosition() lastPosition()
+*/
+QTextCursor QTextFrame::firstCursorPosition() const
+{
+ Q_D(const QTextFrame);
+ return QTextCursor(d->pieceTable, firstPosition());
+}
+
+/*!
+ Returns the last cursor position inside the frame.
+
+ \sa firstCursorPosition() firstPosition() lastPosition()
+*/
+QTextCursor QTextFrame::lastCursorPosition() const
+{
+ Q_D(const QTextFrame);
+ return QTextCursor(d->pieceTable, lastPosition());
+}
+
+/*!
+ Returns the first document position inside the frame.
+
+ \sa lastPosition() firstCursorPosition() lastCursorPosition()
+*/
+int QTextFrame::firstPosition() const
+{
+ Q_D(const QTextFrame);
+ if (!d->fragment_start)
+ return 0;
+ return d->pieceTable->fragmentMap().position(d->fragment_start) + 1;
+}
+
+/*!
+ Returns the last document position inside the frame.
+
+ \sa firstPosition() firstCursorPosition() lastCursorPosition()
+*/
+int QTextFrame::lastPosition() const
+{
+ Q_D(const QTextFrame);
+ if (!d->fragment_end)
+ return d->pieceTable->length() - 1;
+ return d->pieceTable->fragmentMap().position(d->fragment_end);
+}
+
+/*!
+ \internal
+*/
+QTextFrameLayoutData *QTextFrame::layoutData() const
+{
+ Q_D(const QTextFrame);
+ return d->layoutData;
+}
+
+/*!
+ \internal
+*/
+void QTextFrame::setLayoutData(QTextFrameLayoutData *data)
+{
+ Q_D(QTextFrame);
+ delete d->layoutData;
+ d->layoutData = data;
+}
+
+
+
+void QTextFramePrivate::fragmentAdded(const QChar &type, uint fragment)
+{
+ if (type == QTextBeginningOfFrame) {
+ Q_ASSERT(!fragment_start);
+ fragment_start = fragment;
+ } else if (type == QTextEndOfFrame) {
+ Q_ASSERT(!fragment_end);
+ fragment_end = fragment;
+ } else if (type == QChar::ObjectReplacementCharacter) {
+ Q_ASSERT(!fragment_start);
+ Q_ASSERT(!fragment_end);
+ fragment_start = fragment;
+ fragment_end = fragment;
+ } else {
+ Q_ASSERT(false);
+ }
+}
+
+void QTextFramePrivate::fragmentRemoved(const QChar &type, uint fragment)
+{
+ Q_UNUSED(fragment); // --release warning
+ if (type == QTextBeginningOfFrame) {
+ Q_ASSERT(fragment_start == fragment);
+ fragment_start = 0;
+ } else if (type == QTextEndOfFrame) {
+ Q_ASSERT(fragment_end == fragment);
+ fragment_end = 0;
+ } else if (type == QChar::ObjectReplacementCharacter) {
+ Q_ASSERT(fragment_start == fragment);
+ Q_ASSERT(fragment_end == fragment);
+ fragment_start = 0;
+ fragment_end = 0;
+ } else {
+ Q_ASSERT(false);
+ }
+ remove_me();
+}
+
+
+void QTextFramePrivate::remove_me()
+{
+ Q_Q(QTextFrame);
+ if (fragment_start == 0 && fragment_end == 0
+ && !parentFrame) {
+ q->document()->docHandle()->deleteObject(q);
+ return;
+ }
+
+ if (!parentFrame)
+ return;
+
+ int index = parentFrame->d_func()->childFrames.indexOf(q);
+
+ // iterator over all children and move them to the parent
+ for (int i = 0; i < childFrames.size(); ++i) {
+ QTextFrame *c = childFrames.at(i);
+ parentFrame->d_func()->childFrames.insert(index, c);
+ c->d_func()->parentFrame = parentFrame;
+ ++index;
+ }
+ Q_ASSERT(parentFrame->d_func()->childFrames.at(index) == q);
+ parentFrame->d_func()->childFrames.removeAt(index);
+
+ childFrames.clear();
+ parentFrame = 0;
+}
+
+/*!
+ \class QTextFrame::iterator
+ \reentrant
+
+ \brief The iterator class provides an iterator for reading
+ the contents of a QTextFrame.
+
+ \ingroup text
+
+ A frame consists of an arbitrary sequence of \l{QTextBlock}s and
+ child \l{QTextFrame}s. This class provides a way to iterate over the
+ child objects of a frame, and read their contents. It does not provide
+ a way to modify the contents of the frame.
+
+*/
+
+/*!
+ \fn bool QTextFrame::iterator::atEnd() const
+
+ Returns true if the current item is the last item in the text frame.
+*/
+
+/*!
+ Returns an iterator pointing to the first document element inside the frame.
+
+ \sa end()
+*/
+QTextFrame::iterator QTextFrame::begin() const
+{
+ const QTextDocumentPrivate *priv = docHandle();
+ int b = priv->blockMap().findNode(firstPosition());
+ int e = priv->blockMap().findNode(lastPosition()+1);
+ return iterator(const_cast<QTextFrame *>(this), b, b, e);
+}
+
+/*!
+ Returns an iterator pointing to the last document element inside the frame.
+
+ \sa begin()
+*/
+QTextFrame::iterator QTextFrame::end() const
+{
+ const QTextDocumentPrivate *priv = docHandle();
+ int b = priv->blockMap().findNode(firstPosition());
+ int e = priv->blockMap().findNode(lastPosition()+1);
+ return iterator(const_cast<QTextFrame *>(this), e, b, e);
+}
+
+/*!
+ Constructs an invalid iterator.
+*/
+QTextFrame::iterator::iterator()
+{
+ f = 0;
+ b = 0;
+ e = 0;
+ cf = 0;
+ cb = 0;
+}
+
+/*!
+ \internal
+*/
+QTextFrame::iterator::iterator(QTextFrame *frame, int block, int begin, int end)
+{
+ f = frame;
+ b = begin;
+ e = end;
+ cf = 0;
+ cb = block;
+}
+
+/*!
+ Copy constructor. Constructs a copy of the \a other iterator.
+*/
+QTextFrame::iterator::iterator(const iterator &other)
+{
+ f = other.f;
+ b = other.b;
+ e = other.e;
+ cf = other.cf;
+ cb = other.cb;
+}
+
+/*!
+ Assigns \a other to this iterator and returns a reference to
+ this iterator.
+*/
+QTextFrame::iterator &QTextFrame::iterator::operator=(const iterator &other)
+{
+ f = other.f;
+ b = other.b;
+ e = other.e;
+ cf = other.cf;
+ cb = other.cb;
+ return *this;
+}
+
+/*!
+ Returns the current frame pointed to by the iterator, or 0 if the
+ iterator currently points to a block.
+
+ \sa currentBlock()
+*/
+QTextFrame *QTextFrame::iterator::currentFrame() const
+{
+ return cf;
+}
+
+/*!
+ Returns the current block the iterator points to. If the iterator
+ points to a child frame, the returned block is invalid.
+
+ \sa currentFrame()
+*/
+QTextBlock QTextFrame::iterator::currentBlock() const
+{
+ if (!f)
+ return QTextBlock();
+ return QTextBlock(f->docHandle(), cb);
+}
+
+/*!
+ Moves the iterator to the next frame or block.
+
+ \sa currentBlock() currentFrame()
+*/
+QTextFrame::iterator &QTextFrame::iterator::operator++()
+{
+ const QTextDocumentPrivate *priv = f->docHandle();
+ const QTextDocumentPrivate::BlockMap &map = priv->blockMap();
+ if (cf) {
+ int end = cf->lastPosition() + 1;
+ cb = map.findNode(end);
+ cf = 0;
+ } else if (cb) {
+ cb = map.next(cb);
+ if (cb == e)
+ return *this;
+
+ if (!f->d_func()->childFrames.isEmpty()) {
+ int pos = map.position(cb);
+ // check if we entered a frame
+ QTextDocumentPrivate::FragmentIterator frag = priv->find(pos-1);
+ if (priv->buffer().at(frag->stringPosition) != QChar::ParagraphSeparator) {
+ QTextFrame *nf = qobject_cast<QTextFrame *>(priv->objectForFormat(frag->format));
+ if (nf) {
+ if (priv->buffer().at(frag->stringPosition) == QTextBeginningOfFrame && nf != f) {
+ cf = nf;
+ cb = 0;
+ } else {
+ Q_ASSERT(priv->buffer().at(frag->stringPosition) != QTextEndOfFrame);
+ }
+ }
+ }
+ }
+ }
+ return *this;
+}
+
+/*!
+ Moves the iterator to the previous frame or block.
+
+ \sa currentBlock() currentFrame()
+*/
+QTextFrame::iterator &QTextFrame::iterator::operator--()
+{
+ const QTextDocumentPrivate *priv = f->docHandle();
+ const QTextDocumentPrivate::BlockMap &map = priv->blockMap();
+ if (cf) {
+ int start = cf->firstPosition() - 1;
+ cb = map.findNode(start);
+ cf = 0;
+ } else {
+ if (cb == b)
+ goto end;
+ if (cb != e) {
+ int pos = map.position(cb);
+ // check if we have to enter a frame
+ QTextDocumentPrivate::FragmentIterator frag = priv->find(pos-1);
+ if (priv->buffer().at(frag->stringPosition) != QChar::ParagraphSeparator) {
+ QTextFrame *pf = qobject_cast<QTextFrame *>(priv->objectForFormat(frag->format));
+ if (pf) {
+ if (priv->buffer().at(frag->stringPosition) == QTextBeginningOfFrame) {
+ Q_ASSERT(pf == f);
+ } else if (priv->buffer().at(frag->stringPosition) == QTextEndOfFrame) {
+ Q_ASSERT(pf != f);
+ cf = pf;
+ cb = 0;
+ goto end;
+ }
+ }
+ }
+ }
+ cb = map.previous(cb);
+ }
+ end:
+ return *this;
+}
+
+/*!
+ \class QTextBlockUserData
+ \reentrant
+
+ \brief The QTextBlockUserData class is used to associate custom data with blocks of text.
+ \since 4.1
+
+ \ingroup text
+
+ QTextBlockUserData provides an abstract interface for container classes that are used
+ to associate application-specific user data with text blocks in a QTextDocument.
+
+ Generally, subclasses of this class provide functions to allow data to be stored
+ and retrieved, and instances are attached to blocks of text using
+ QTextBlock::setUserData(). This makes it possible to store additional data per text
+ block in a way that can be retrieved safely by the application.
+
+ Each subclass should provide a reimplementation of the destructor to ensure that any
+ private data is automatically cleaned up when user data objects are deleted.
+
+ \sa QTextBlock
+*/
+
+/*!
+ Destroys the user data.
+*/
+QTextBlockUserData::~QTextBlockUserData()
+{
+}
+
+/*!
+ \class QTextBlock
+ \reentrant
+
+ \brief The QTextBlock class provides a container for text fragments in a
+ QTextDocument.
+
+ \ingroup text
+
+ A text block encapsulates a block or paragraph of text in a QTextDocument.
+ QTextBlock provides read-only access to the block/paragraph structure of
+ QTextDocuments. It is mainly of use if you want to implement your own
+ layouts for the visual representation of a QTextDocument, or if you want to
+ iterate over a document and write out the contents in your own custom
+ format.
+
+ Text blocks are created by their parent documents. If you need to create
+ a new text block, or modify the contents of a document while examining its
+ contents, use the cursor-based interface provided by QTextCursor instead.
+
+ Each text block is located at a specific position() in a document().
+ The contents of the block can be obtained by using the text() function.
+ The length() function determines the block's size within the document
+ (including formatting characters).
+ The visual properties of the block are determined by its text layout(),
+ its charFormat(), and its blockFormat().
+
+ The next() and previous() functions enable iteration over consecutive
+ valid blocks in a document under the condition that the document is not
+ modified by other means during the iteration process. Note that, although
+ blocks are returned in sequence, adjacent blocks may come from different
+ places in the document structure. The validity of a block can be determined
+ by calling isValid().
+
+ QTextBlock provides comparison operators to make it easier to work with
+ blocks: \l operator==() compares two block for equality, \l operator!=()
+ compares two blocks for inequality, and \l operator<() determines whether
+ a block precedes another in the same document.
+
+ \img qtextblock-sequence.png
+
+ \sa QTextBlockFormat QTextCharFormat QTextFragment
+ */
+
+/*!
+ \fn QTextBlock::QTextBlock(QTextDocumentPrivate *priv, int b)
+
+ \internal
+*/
+
+/*!
+ \fn QTextBlock::QTextBlock()
+
+ \internal
+*/
+
+/*!
+ \fn QTextBlock::QTextBlock(const QTextBlock &other)
+
+ Copies the \a other text block's attributes to this text block.
+*/
+
+/*!
+ \fn bool QTextBlock::isValid() const
+
+ Returns true if this text block is valid; otherwise returns false.
+*/
+
+/*!
+ \fn QTextBlock &QTextBlock::operator=(const QTextBlock &other)
+
+ Assigns the \a other text block to this text block.
+*/
+
+/*!
+ \fn bool QTextBlock::operator==(const QTextBlock &other) const
+
+ Returns true if this text block is the same as the \a other text
+ block.
+*/
+
+/*!
+ \fn bool QTextBlock::operator!=(const QTextBlock &other) const
+
+ Returns true if this text block is different from the \a other
+ text block.
+*/
+
+/*!
+ \fn bool QTextBlock::operator<(const QTextBlock &other) const
+
+ Returns true if this text block occurs before the \a other text
+ block in the document.
+*/
+
+/*!
+ \class QTextBlock::iterator
+ \reentrant
+
+ \brief The QTextBlock::iterator class provides an iterator for reading
+ the contents of a QTextBlock.
+
+ \ingroup text
+
+ A block consists of a sequence of text fragments. This class provides
+ a way to iterate over these, and read their contents. It does not provide
+ a way to modify the internal structure or contents of the block.
+
+ An iterator can be constructed and used to access the fragments within
+ a text block in the following way:
+
+ \snippet doc/src/snippets/textblock-fragments/xmlwriter.cpp 4
+ \snippet doc/src/snippets/textblock-fragments/xmlwriter.cpp 7
+
+ \sa QTextFragment
+*/
+
+/*!
+ \typedef QTextBlock::Iterator
+
+ Qt-style synonym for QTextBlock::iterator.
+*/
+
+/*!
+ \fn QTextBlock::iterator::iterator()
+
+ Constructs an iterator for this text block.
+*/
+
+/*!
+ \fn QTextBlock::iterator::iterator(const iterator &other)
+
+ Copy constructor. Constructs a copy of the \a other iterator.
+*/
+
+/*!
+ \fn bool QTextBlock::iterator::atEnd() const
+
+ Returns true if the current item is the last item in the text block.
+*/
+
+/*!
+ \fn bool QTextBlock::iterator::operator==(const iterator &other) const
+
+ Retuns true if this iterator is the same as the \a other iterator;
+ otherwise returns false.
+*/
+
+/*!
+ \fn bool QTextBlock::iterator::operator!=(const iterator &other) const
+
+ Retuns true if this iterator is different from the \a other iterator;
+ otherwise returns false.
+*/
+
+/*!
+ \fn QTextBlock::iterator QTextBlock::iterator::operator++(int)
+
+ The postfix ++ operator (\c{i++}) advances the iterator to the
+ next item in the text block and returns an iterator to the old current
+ item.
+*/
+
+/*!
+ \fn QTextBlock::iterator QTextBlock::iterator::operator--(int)
+
+ The postfix -- operator (\c{i--}) makes the preceding item current and
+ returns an iterator to the old current item.
+*/
+
+/*!
+ \fn QTextDocumentPrivate *QTextBlock::docHandle() const
+
+ \internal
+*/
+
+/*!
+ \fn int QTextBlock::fragmentIndex() const
+
+ \internal
+*/
+
+/*!
+ Returns the index of the block's first character within the document.
+ */
+int QTextBlock::position() const
+{
+ if (!p || !n)
+ return 0;
+
+ return p->blockMap().position(n);
+}
+
+/*!
+ Returns the length of the block in characters.
+
+ \note The length returned includes all formatting characters,
+ for example, newline.
+
+ \sa text() charFormat() blockFormat()
+ */
+int QTextBlock::length() const
+{
+ if (!p || !n)
+ return 0;
+
+ return p->blockMap().size(n);
+}
+
+/*!
+ Returns true if the given \a position is located within the text
+ block; otherwise returns false.
+ */
+bool QTextBlock::contains(int position) const
+{
+ if (!p || !n)
+ return false;
+
+ int pos = p->blockMap().position(n);
+ int len = p->blockMap().size(n);
+ return position >= pos && position < pos + len;
+}
+
+/*!
+ Returns the QTextLayout that is used to lay out and display the
+ block's contents.
+
+ Note that the returned QTextLayout object can only be modified from the
+ documentChanged implementation of a QAbstractTextDocumentLayout subclass.
+ Any changes applied from the outside cause undefined behavior.
+
+ \sa clearLayout()
+ */
+QTextLayout *QTextBlock::layout() const
+{
+ if (!p || !n)
+ return 0;
+
+ const QTextBlockData *b = p->blockMap().fragment(n);
+ if (!b->layout)
+ b->layout = new QTextLayout(*this);
+ return b->layout;
+}
+
+/*!
+ \since 4.4
+ Clears the QTextLayout that is used to lay out and display the
+ block's contents.
+
+ \sa layout()
+ */
+void QTextBlock::clearLayout()
+{
+ if (!p || !n)
+ return;
+
+ const QTextBlockData *b = p->blockMap().fragment(n);
+ if (b->layout)
+ b->layout->clearLayout();
+}
+
+/*!
+ Returns the QTextBlockFormat that describes block-specific properties.
+
+ \sa charFormat()
+ */
+QTextBlockFormat QTextBlock::blockFormat() const
+{
+ if (!p || !n)
+ return QTextFormat().toBlockFormat();
+
+ return p->formatCollection()->blockFormat(p->blockMap().fragment(n)->format);
+}
+
+/*!
+ Returns an index into the document's internal list of block formats
+ for the text block's format.
+
+ \sa QTextDocument::allFormats()
+*/
+int QTextBlock::blockFormatIndex() const
+{
+ if (!p || !n)
+ return -1;
+
+ return p->blockMap().fragment(n)->format;
+}
+
+/*!
+ Returns the QTextCharFormat that describes the block's character
+ format. The block's character format is used when inserting text into
+ an empty block.
+
+ \sa blockFormat()
+ */
+QTextCharFormat QTextBlock::charFormat() const
+{
+ if (!p || !n)
+ return QTextFormat().toCharFormat();
+
+ return p->formatCollection()->charFormat(charFormatIndex());
+}
+
+/*!
+ Returns an index into the document's internal list of character formats
+ for the text block's character format.
+
+ \sa QTextDocument::allFormats()
+*/
+int QTextBlock::charFormatIndex() const
+{
+ if (!p || !n)
+ return -1;
+
+ return p->blockCharFormatIndex(n);
+}
+
+/*!
+ Returns the block's contents as plain text.
+
+ \sa length() charFormat() blockFormat()
+ */
+QString QTextBlock::text() const
+{
+ if (!p || !n)
+ return QString();
+
+ const QString buffer = p->buffer();
+ QString text;
+ text.reserve(length());
+
+ const int pos = position();
+ QTextDocumentPrivate::FragmentIterator it = p->find(pos);
+ QTextDocumentPrivate::FragmentIterator end = p->find(pos + length() - 1); // -1 to omit the block separator char
+ for (; it != end; ++it) {
+ const QTextFragmentData * const frag = it.value();
+ text += QString::fromRawData(buffer.constData() + frag->stringPosition, frag->size_array[0]);
+ }
+
+ return text;
+}
+
+
+/*!
+ Returns the text document this text block belongs to, or 0 if the
+ text block does not belong to any document.
+*/
+const QTextDocument *QTextBlock::document() const
+{
+ return p ? p->document() : 0;
+}
+
+/*!
+ If the block represents a list item, returns the list that the item belongs
+ to; otherwise returns 0.
+*/
+QTextList *QTextBlock::textList() const
+{
+ if (!isValid())
+ return 0;
+
+ const QTextBlockFormat fmt = blockFormat();
+ QTextObject *obj = p->document()->objectForFormat(fmt);
+ return qobject_cast<QTextList *>(obj);
+}
+
+/*!
+ \since 4.1
+
+ Returns a pointer to a QTextBlockUserData object if previously set with
+ setUserData() or a null pointer.
+*/
+QTextBlockUserData *QTextBlock::userData() const
+{
+ if (!p || !n)
+ return 0;
+
+ const QTextBlockData *b = p->blockMap().fragment(n);
+ return b->userData;
+}
+
+/*!
+ \since 4.1
+
+ Attaches the given \a data object to the text block.
+
+ QTextBlockUserData can be used to store custom settings. 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. The user data object is
+ not stored in the undo history, so it will not be available after
+ undoing the deletion of a text block.
+
+ For example, if you write a programming editor in an IDE, you may
+ want to let your user set breakpoints visually in your code for an
+ integrated debugger. In a programming editor a line of text
+ usually corresponds to one QTextBlock. The QTextBlockUserData
+ interface allows the developer to store data for each QTextBlock,
+ like for example in which lines of the source code the user has a
+ breakpoint set. Of course this could also be stored externally,
+ but by storing it inside the QTextDocument, it will for example be
+ automatically deleted when the user deletes the associated
+ line. It's really just a way to store custom information in the
+ QTextDocument without using custom properties in QTextFormat which
+ would affect the undo/redo stack.
+*/
+void QTextBlock::setUserData(QTextBlockUserData *data)
+{
+ if (!p || !n)
+ return;
+
+ const QTextBlockData *b = p->blockMap().fragment(n);
+ if (data != b->userData)
+ delete b->userData;
+ b->userData = data;
+}
+
+/*!
+ \since 4.1
+
+ Returns the integer value previously set with setUserState() or -1.
+*/
+int QTextBlock::userState() const
+{
+ if (!p || !n)
+ return -1;
+
+ const QTextBlockData *b = p->blockMap().fragment(n);
+ return b->userState;
+}
+
+/*!
+ \since 4.1
+
+ Stores the specified \a state integer value in the text block. This may be
+ useful for example in a syntax highlighter to store a text parsing state.
+*/
+void QTextBlock::setUserState(int state)
+{
+ if (!p || !n)
+ return;
+
+ const QTextBlockData *b = p->blockMap().fragment(n);
+ b->userState = state;
+}
+
+/*!
+ \since 4.4
+
+ Returns the blocks revision.
+
+ \sa setRevision(), QTextDocument::revision()
+*/
+int QTextBlock::revision() const
+{
+ if (!p || !n)
+ return -1;
+
+ const QTextBlockData *b = p->blockMap().fragment(n);
+ return b->revision;
+}
+
+/*!
+ \since 4.4
+
+ Sets a blocks revision to \a rev.
+
+ \sa revision(), QTextDocument::revision()
+*/
+void QTextBlock::setRevision(int rev)
+{
+ if (!p || !n)
+ return;
+
+ const QTextBlockData *b = p->blockMap().fragment(n);
+ b->revision = rev;
+}
+
+/*!
+ \since 4.4
+
+ Returns true if the block is visible; otherwise returns false.
+
+ \sa setVisible()
+*/
+bool QTextBlock::isVisible() const
+{
+ if (!p || !n)
+ return true;
+
+ const QTextBlockData *b = p->blockMap().fragment(n);
+ return !b->hidden;
+}
+
+/*!
+ \since 4.4
+
+ Sets the block's visibility to \a visible.
+
+ \sa isVisible()
+*/
+void QTextBlock::setVisible(bool visible)
+{
+ if (!p || !n)
+ return;
+
+ const QTextBlockData *b = p->blockMap().fragment(n);
+ b->hidden = !visible;
+}
+
+
+/*!
+\since 4.4
+
+ Returns the number of this block, or -1 if the block is invalid.
+
+ \sa QTextCursor::blockNumber()
+
+*/
+int QTextBlock::blockNumber() const
+{
+ if (!p || !n)
+ return -1;
+ return p->blockMap().position(n, 1);
+}
+
+/*!
+\since 4.5
+
+ Returns the first line number of this block, or -1 if the block is invalid.
+ Unless the layout supports it, the line number is identical to the block number.
+
+ \sa QTextBlock::blockNumber()
+
+*/
+int QTextBlock::firstLineNumber() const
+{
+ if (!p || !n)
+ return -1;
+ return p->blockMap().position(n, 2);
+}
+
+
+/*!
+\since 4.5
+
+Sets the line count to \a count.
+
+/sa lineCount()
+*/
+void QTextBlock::setLineCount(int count)
+{
+ if (!p || !n)
+ return;
+ p->blockMap().setSize(n, count, 2);
+}
+/*!
+\since 4.5
+
+Returns the line count. Not all document layouts support this feature.
+
+\sa setLineCount()
+ */
+int QTextBlock::lineCount() const
+{
+ if (!p || !n)
+ return -1;
+ return p->blockMap().size(n, 2);
+}
+
+
+/*!
+ Returns a text block iterator pointing to the beginning of the
+ text block.
+
+ \sa end()
+*/
+QTextBlock::iterator QTextBlock::begin() const
+{
+ if (!p || !n)
+ return iterator();
+
+ int pos = position();
+ int len = length() - 1; // exclude the fragment that holds the paragraph separator
+ int b = p->fragmentMap().findNode(pos);
+ int e = p->fragmentMap().findNode(pos+len);
+ return iterator(p, b, e, b);
+}
+
+/*!
+ Returns a text block iterator pointing to the end of the text
+ block.
+
+ \sa begin() next() previous()
+*/
+QTextBlock::iterator QTextBlock::end() const
+{
+ if (!p || !n)
+ return iterator();
+
+ int pos = position();
+ int len = length() - 1; // exclude the fragment that holds the paragraph separator
+ int b = p->fragmentMap().findNode(pos);
+ int e = p->fragmentMap().findNode(pos+len);
+ return iterator(p, b, e, e);
+}
+
+
+/*!
+ Returns the text block in the document after this block, or an empty
+ text block if this is the last one.
+
+ Note that the next block may be in a different frame or table to this block.
+
+ \sa previous() begin() end()
+*/
+QTextBlock QTextBlock::next() const
+{
+ if (!p)
+ return QTextBlock();
+
+ return QTextBlock(p, p->blockMap().next(n));
+}
+
+/*!
+ Returns the text block in the document before this block, or an empty text
+ block if this is the first one.
+
+ Note that the next block may be in a different frame or table to this block.
+
+ \sa next() begin() end()
+*/
+QTextBlock QTextBlock::previous() const
+{
+ if (!p)
+ return QTextBlock();
+
+ return QTextBlock(p, p->blockMap().previous(n));
+}
+
+
+/*!
+ Returns the text fragment the iterator currently points to.
+*/
+QTextFragment QTextBlock::iterator::fragment() const
+{
+ int ne = n;
+ int formatIndex = p->fragmentMap().fragment(n)->format;
+ do {
+ ne = p->fragmentMap().next(ne);
+ } while (ne != e && p->fragmentMap().fragment(ne)->format == formatIndex);
+ return QTextFragment(p, n, ne);
+}
+
+/*!
+ The prefix ++ operator (\c{++i}) advances the iterator to the
+ next item in the hash and returns an iterator to the new current
+ item.
+*/
+
+QTextBlock::iterator &QTextBlock::iterator::operator++()
+{
+ int ne = n;
+ int formatIndex = p->fragmentMap().fragment(n)->format;
+ do {
+ ne = p->fragmentMap().next(ne);
+ } while (ne != e && p->fragmentMap().fragment(ne)->format == formatIndex);
+ n = ne;
+ return *this;
+}
+
+/*!
+ The prefix -- operator (\c{--i}) makes the preceding item
+ current and returns an iterator pointing to the new current item.
+*/
+
+QTextBlock::iterator &QTextBlock::iterator::operator--()
+{
+ n = p->fragmentMap().previous(n);
+
+ if (n == b)
+ return *this;
+
+ int formatIndex = p->fragmentMap().fragment(n)->format;
+ int last = n;
+
+ while (n != b && p->fragmentMap().fragment(n)->format != formatIndex) {
+ last = n;
+ n = p->fragmentMap().previous(n);
+ }
+
+ n = last;
+ return *this;
+}
+
+
+/*!
+ \class QTextFragment
+ \reentrant
+
+ \brief The QTextFragment class holds a piece of text in a
+ QTextDocument with a single QTextCharFormat.
+
+ \ingroup text
+
+ A text fragment describes a piece of text that is stored with a single
+ character format. Text in which the character format changes can be
+ represented by sequences of text fragments with different formats.
+
+ If the user edits the text in a fragment and introduces a different
+ character format, the fragment's text will be split at each point where
+ the format changes, and new fragments will be created.
+ For example, changing the style of some text in the middle of a
+ sentence will cause the fragment to be broken into three separate fragments:
+ the first and third with the same format as before, and the second with
+ the new style. The first fragment will contain the text from the beginning
+ of the sentence, the second will contain the text from the middle, and the
+ third takes the text from the end of the sentence.
+
+ \img qtextfragment-split.png
+
+ A fragment's text and character format can be obtained with the text()
+ and charFormat() functions. The length() function gives the length of
+ the text in the fragment. position() gives the position in the document
+ of the start of the fragment. To determine whether the fragment contains
+ a particular position within the document, use the contains() function.
+
+ \sa QTextDocument, {Rich Text Document Structure}
+*/
+
+/*!
+ \fn QTextFragment::QTextFragment(const QTextDocumentPrivate *priv, int f, int fe)
+ \internal
+*/
+
+/*!
+ \fn QTextFragment::QTextFragment()
+
+ Creates a new empty text fragment.
+*/
+
+/*!
+ \fn QTextFragment::QTextFragment(const QTextFragment &other)
+
+ Copies the content (text and format) of the \a other text fragment
+ to this text fragment.
+*/
+
+/*!
+ \fn QTextFragment &QTextFragment::operator=(const QTextFragment
+ &other)
+
+ Assigns the content (text and format) of the \a other text fragment
+ to this text fragment.
+*/
+
+/*!
+ \fn bool QTextFragment::isValid() const
+
+ Returns true if this is a valid text fragment (i.e. has a valid
+ position in a document); otherwise returns false.
+*/
+
+/*!
+ \fn bool QTextFragment::operator==(const QTextFragment &other) const
+
+ Returns true if this text fragment is the same (at the same
+ position) as the \a other text fragment; otherwise returns false.
+*/
+
+/*!
+ \fn bool QTextFragment::operator!=(const QTextFragment &other) const
+
+ Returns true if this text fragment is different (at a different
+ position) from the \a other text fragment; otherwise returns
+ false.
+*/
+
+/*!
+ \fn bool QTextFragment::operator<(const QTextFragment &other) const
+
+ Returns true if this text fragment appears earlier in the document
+ than the \a other text fragment; otherwise returns false.
+*/
+
+
+/*!
+ Returns the position of this text fragment in the document.
+*/
+int QTextFragment::position() const
+{
+ if (!p || !n)
+ return 0; // ### -1 instead?
+
+ return p->fragmentMap().position(n);
+}
+
+/*!
+ Returns the number of characters in the text fragment.
+
+ \sa text()
+*/
+int QTextFragment::length() const
+{
+ if (!p || !n)
+ return 0;
+
+ int len = 0;
+ int f = n;
+ while (f != ne) {
+ len += p->fragmentMap().size(f);
+ f = p->fragmentMap().next(f);
+ }
+ return len;
+}
+
+/*!
+ Returns true if the text fragment contains the text at the given
+ \a position in the document; otherwise returns false.
+*/
+bool QTextFragment::contains(int position) const
+{
+ if (!p || !n)
+ return false;
+ int pos = this->position();
+ return position >= pos && position < pos + length();
+}
+
+/*!
+ Returns the text fragment's character format.
+
+ \sa text()
+*/
+QTextCharFormat QTextFragment::charFormat() const
+{
+ if (!p || !n)
+ return QTextCharFormat();
+ const QTextFragmentData *data = p->fragmentMap().fragment(n);
+ return p->formatCollection()->charFormat(data->format);
+}
+
+/*!
+ Returns an index into the document's internal list of character formats
+ for the text fragment's character format.
+
+ \sa QTextDocument::allFormats()
+*/
+int QTextFragment::charFormatIndex() const
+{
+ if (!p || !n)
+ return -1;
+ const QTextFragmentData *data = p->fragmentMap().fragment(n);
+ return data->format;
+}
+
+/*!
+ Returns the text fragment's as plain text.
+
+ \sa length(), charFormat()
+*/
+QString QTextFragment::text() const
+{
+ if (!p || !n)
+ return QString();
+
+ QString result;
+ QString buffer = p->buffer();
+ int f = n;
+ while (f != ne) {
+ const QTextFragmentData * const frag = p->fragmentMap().fragment(f);
+ result += QString(buffer.constData() + frag->stringPosition, frag->size_array[0]);
+ f = p->fragmentMap().next(f);
+ }
+ return result;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/text/qtextobject.h b/src/gui/text/qtextobject.h
new file mode 100644
index 0000000000..5175441374
--- /dev/null
+++ b/src/gui/text/qtextobject.h
@@ -0,0 +1,328 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTOBJECT_H
+#define QTEXTOBJECT_H
+
+#include <QtCore/qobject.h>
+#include <QtGui/qtextformat.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Gui)
+
+class QTextObjectPrivate;
+class QTextDocument;
+class QTextDocumentPrivate;
+class QTextCursor;
+class QTextBlock;
+class QTextFragment;
+class QTextLayout;
+class QTextList;
+
+class Q_GUI_EXPORT QTextObject : public QObject
+{
+ Q_OBJECT
+
+protected:
+ explicit QTextObject(QTextDocument *doc);
+ ~QTextObject();
+
+ void setFormat(const QTextFormat &format);
+
+public:
+ QTextFormat format() const;
+ int formatIndex() const;
+
+ QTextDocument *document() const;
+
+ int objectIndex() const;
+
+ QTextDocumentPrivate *docHandle() const;
+
+protected:
+ QTextObject(QTextObjectPrivate &p, QTextDocument *doc);
+
+private:
+ Q_DECLARE_PRIVATE(QTextObject)
+ Q_DISABLE_COPY(QTextObject)
+ friend class QTextDocumentPrivate;
+};
+
+class QTextBlockGroupPrivate;
+class Q_GUI_EXPORT QTextBlockGroup : public QTextObject
+{
+ Q_OBJECT
+
+protected:
+ explicit QTextBlockGroup(QTextDocument *doc);
+ ~QTextBlockGroup();
+
+ virtual void blockInserted(const QTextBlock &block);
+ virtual void blockRemoved(const QTextBlock &block);
+ virtual void blockFormatChanged(const QTextBlock &block);
+
+ QList<QTextBlock> blockList() const;
+
+protected:
+ QTextBlockGroup(QTextBlockGroupPrivate &p, QTextDocument *doc);
+private:
+ Q_DECLARE_PRIVATE(QTextBlockGroup)
+ Q_DISABLE_COPY(QTextBlockGroup)
+ friend class QTextDocumentPrivate;
+};
+
+class Q_GUI_EXPORT QTextFrameLayoutData {
+public:
+ virtual ~QTextFrameLayoutData();
+};
+
+class QTextFramePrivate;
+class Q_GUI_EXPORT QTextFrame : public QTextObject
+{
+ Q_OBJECT
+
+public:
+ explicit QTextFrame(QTextDocument *doc);
+ ~QTextFrame();
+
+ inline void setFrameFormat(const QTextFrameFormat &format);
+ QTextFrameFormat frameFormat() const { return QTextObject::format().toFrameFormat(); }
+
+ QTextCursor firstCursorPosition() const;
+ QTextCursor lastCursorPosition() const;
+ int firstPosition() const;
+ int lastPosition() const;
+
+ QTextFrameLayoutData *layoutData() const;
+ void setLayoutData(QTextFrameLayoutData *data);
+
+ QList<QTextFrame *> childFrames() const;
+ QTextFrame *parentFrame() const;
+
+ class Q_GUI_EXPORT iterator {
+ QTextFrame *f;
+ int b;
+ int e;
+ QTextFrame *cf;
+ int cb;
+
+ friend class QTextFrame;
+ friend class QTextTableCell;
+ friend class QTextDocumentLayoutPrivate;
+ iterator(QTextFrame *frame, int block, int begin, int end);
+ public:
+ iterator();
+ iterator(const iterator &o);
+ iterator &operator=(const iterator &o);
+
+ QTextFrame *parentFrame() const { return f; }
+
+ QTextFrame *currentFrame() const;
+ QTextBlock currentBlock() const;
+
+ bool atEnd() const { return !cf && cb == e; }
+
+ inline bool operator==(const iterator &o) const { return f == o.f && cf == o.cf && cb == o.cb; }
+ inline bool operator!=(const iterator &o) const { return f != o.f || cf != o.cf || cb != o.cb; }
+ iterator &operator++();
+ inline iterator operator++(int) { iterator tmp = *this; operator++(); return tmp; }
+ iterator &operator--();
+ inline iterator operator--(int) { iterator tmp = *this; operator--(); return tmp; }
+ };
+
+ friend class iterator;
+ // more Qt
+ typedef iterator Iterator;
+
+ iterator begin() const;
+ iterator end() const;
+
+protected:
+ QTextFrame(QTextFramePrivate &p, QTextDocument *doc);
+private:
+ friend class QTextDocumentPrivate;
+ Q_DECLARE_PRIVATE(QTextFrame)
+ Q_DISABLE_COPY(QTextFrame)
+};
+Q_DECLARE_TYPEINFO(QTextFrame::iterator, Q_MOVABLE_TYPE);
+
+inline void QTextFrame::setFrameFormat(const QTextFrameFormat &aformat)
+{ QTextObject::setFormat(aformat); }
+
+class Q_GUI_EXPORT QTextBlockUserData {
+public:
+ virtual ~QTextBlockUserData();
+};
+
+class Q_GUI_EXPORT QTextBlock
+{
+ friend class QSyntaxHighlighter;
+public:
+ inline QTextBlock(QTextDocumentPrivate *priv, int b) : p(priv), n(b) {}
+ inline QTextBlock() : p(0), n(0) {}
+ inline QTextBlock(const QTextBlock &o) : p(o.p), n(o.n) {}
+ inline QTextBlock &operator=(const QTextBlock &o) { p = o.p; n = o.n; return *this; }
+
+ inline bool isValid() const { return p != 0 && n != 0; }
+
+ inline bool operator==(const QTextBlock &o) const { return p == o.p && n == o.n; }
+ inline bool operator!=(const QTextBlock &o) const { return p != o.p || n != o.n; }
+ inline bool operator<(const QTextBlock &o) const { return position() < o.position(); }
+
+ int position() const;
+ int length() const;
+ bool contains(int position) const;
+
+ QTextLayout *layout() const;
+ void clearLayout();
+ QTextBlockFormat blockFormat() const;
+ int blockFormatIndex() const;
+ QTextCharFormat charFormat() const;
+ int charFormatIndex() const;
+
+ QString text() const;
+
+ const QTextDocument *document() const;
+
+ QTextList *textList() const;
+
+ QTextBlockUserData *userData() const;
+ void setUserData(QTextBlockUserData *data);
+
+ int userState() const;
+ void setUserState(int state);
+
+ int revision() const;
+ void setRevision(int rev);
+
+ bool isVisible() const;
+ void setVisible(bool visible);
+
+ int blockNumber() const;
+ int firstLineNumber() const;
+
+ void setLineCount(int count);
+ int lineCount() const;
+
+ class Q_GUI_EXPORT iterator {
+ const QTextDocumentPrivate *p;
+ int b;
+ int e;
+ int n;
+ friend class QTextBlock;
+ iterator(const QTextDocumentPrivate *priv, int begin, int end, int f) : p(priv), b(begin), e(end), n(f) {}
+ public:
+ iterator() : p(0), b(0), e(0), n(0) {}
+ iterator(const iterator &o) : p(o.p), b(o.b), e(o.e), n(o.n) {}
+
+ QTextFragment fragment() const;
+
+ bool atEnd() const { return n == e; }
+
+ inline bool operator==(const iterator &o) const { return p == o.p && n == o.n; }
+ inline bool operator!=(const iterator &o) const { return p != o.p || n != o.n; }
+ iterator &operator++();
+ inline iterator operator++(int) { iterator tmp = *this; operator++(); return tmp; }
+ iterator &operator--();
+ inline iterator operator--(int) { iterator tmp = *this; operator--(); return tmp; }
+ };
+
+ // more Qt
+ typedef iterator Iterator;
+
+ iterator begin() const;
+ iterator end() const;
+
+ QTextBlock next() const;
+ QTextBlock previous() const;
+
+ inline QTextDocumentPrivate *docHandle() const { return p; }
+ inline int fragmentIndex() const { return n; }
+
+private:
+ QTextDocumentPrivate *p;
+ int n;
+ friend class QTextDocumentPrivate;
+ friend class QTextLayout;
+};
+
+Q_DECLARE_TYPEINFO(QTextBlock, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QTextBlock::iterator, Q_MOVABLE_TYPE);
+
+
+class Q_GUI_EXPORT QTextFragment
+{
+public:
+ inline QTextFragment(const QTextDocumentPrivate *priv, int f, int fe) : p(priv), n(f), ne(fe) {}
+ inline QTextFragment() : p(0), n(0), ne(0) {}
+ inline QTextFragment(const QTextFragment &o) : p(o.p), n(o.n), ne(o.ne) {}
+ inline QTextFragment &operator=(const QTextFragment &o) { p = o.p; n = o.n; ne = o.ne; return *this; }
+
+ inline bool isValid() const { return p && n; }
+
+ inline bool operator==(const QTextFragment &o) const { return p == o.p && n == o.n; }
+ inline bool operator!=(const QTextFragment &o) const { return p != o.p || n != o.n; }
+ inline bool operator<(const QTextFragment &o) const { return position() < o.position(); }
+
+ int position() const;
+ int length() const;
+ bool contains(int position) const;
+
+ QTextCharFormat charFormat() const;
+ int charFormatIndex() const;
+ QString text() const;
+
+private:
+ const QTextDocumentPrivate *p;
+ int n;
+ int ne;
+};
+
+Q_DECLARE_TYPEINFO(QTextFragment, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QTEXTOBJECT_H
diff --git a/src/gui/text/qtextobject_p.h b/src/gui/text/qtextobject_p.h
new file mode 100644
index 0000000000..b00a16a485
--- /dev/null
+++ b/src/gui/text/qtextobject_p.h
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTOBJECT_P_H
+#define QTEXTOBJECT_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 "QtGui/qtextobject.h"
+#include "private/qobject_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QTextDocumentPrivate;
+
+class QTextObjectPrivate : public QObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QTextObject)
+public:
+ QTextDocumentPrivate *pieceTable;
+ int objectIndex;
+};
+
+class QTextBlockGroupPrivate : public QTextObjectPrivate
+{
+ Q_DECLARE_PUBLIC(QTextBlockGroup)
+public:
+
+ typedef QList<QTextBlock> BlockList;
+ BlockList blocks;
+ void markBlocksDirty();
+};
+
+class QTextFrameLayoutData;
+
+class QTextFramePrivate : public QTextObjectPrivate
+{
+ friend class QTextDocumentPrivate;
+ Q_DECLARE_PUBLIC(QTextFrame)
+public:
+
+ virtual void fragmentAdded(const QChar &type, uint fragment);
+ virtual void fragmentRemoved(const QChar &type, uint fragment);
+ void remove_me();
+
+ uint fragment_start;
+ uint fragment_end;
+
+ QTextFrame *parentFrame;
+ QList<QTextFrame *> childFrames;
+ QTextFrameLayoutData *layoutData;
+};
+
+QT_END_NAMESPACE
+
+#endif // QTEXTOBJECT_P_H
diff --git a/src/gui/text/qtextodfwriter.cpp b/src/gui/text/qtextodfwriter.cpp
new file mode 100644
index 0000000000..1edc3b873d
--- /dev/null
+++ b/src/gui/text/qtextodfwriter.cpp
@@ -0,0 +1,818 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qglobal.h>
+
+#ifndef QT_NO_TEXTODFWRITER
+
+#include "qtextodfwriter_p.h"
+
+#include <QImageWriter>
+#include <QTextListFormat>
+#include <QTextList>
+#include <QBuffer>
+#include <QUrl>
+
+#include "qtextdocument_p.h"
+#include "qtexttable.h"
+#include "qtextcursor.h"
+#include "qtextimagehandler_p.h"
+#include "qzipwriter_p.h"
+
+#include <QDebug>
+
+QT_BEGIN_NAMESPACE
+
+/// Convert pixels to postscript point units
+static QString pixelToPoint(qreal pixels)
+{
+ // we hardcode 96 DPI, we do the same in the ODF importer to have a perfect roundtrip.
+ return QString::number(pixels * 72 / 96) + QString::fromLatin1("pt");
+}
+
+// strategies
+class QOutputStrategy {
+public:
+ QOutputStrategy() : contentStream(0), counter(1) { }
+ virtual ~QOutputStrategy() {}
+ virtual void addFile(const QString &fileName, const QString &mimeType, const QByteArray &bytes) = 0;
+
+ QString createUniqueImageName()
+ {
+ return QString::fromLatin1("Pictures/Picture%1").arg(counter++);
+ }
+
+ QIODevice *contentStream;
+ int counter;
+};
+
+class QXmlStreamStrategy : public QOutputStrategy {
+public:
+ QXmlStreamStrategy(QIODevice *device)
+ {
+ contentStream = device;
+ }
+
+ virtual ~QXmlStreamStrategy()
+ {
+ if (contentStream)
+ contentStream->close();
+ }
+ virtual void addFile(const QString &, const QString &, const QByteArray &)
+ {
+ // we ignore this...
+ }
+};
+
+class QZipStreamStrategy : public QOutputStrategy {
+public:
+ QZipStreamStrategy(QIODevice *device)
+ : zip(device),
+ manifestWriter(&manifest)
+ {
+ QByteArray mime("application/vnd.oasis.opendocument.text");
+ zip.setCompressionPolicy(QZipWriter::NeverCompress);
+ zip.addFile(QString::fromLatin1("mimetype"), mime); // for mime-magick
+ zip.setCompressionPolicy(QZipWriter::AutoCompress);
+ contentStream = &content;
+ content.open(QIODevice::WriteOnly);
+ manifest.open(QIODevice::WriteOnly);
+
+ manifestNS = QString::fromLatin1("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0");
+ // prettyfy
+ manifestWriter.setAutoFormatting(true);
+ manifestWriter.setAutoFormattingIndent(1);
+
+ manifestWriter.writeNamespace(manifestNS, QString::fromLatin1("manifest"));
+ manifestWriter.writeStartDocument();
+ manifestWriter.writeStartElement(manifestNS, QString::fromLatin1("manifest"));
+ addFile(QString::fromLatin1("/"), QString::fromLatin1("application/vnd.oasis.opendocument.text"));
+ addFile(QString::fromLatin1("content.xml"), QString::fromLatin1("text/xml"));
+ }
+
+ ~QZipStreamStrategy()
+ {
+ manifestWriter.writeEndDocument();
+ manifest.close();
+ zip.addFile(QString::fromLatin1("META-INF/manifest.xml"), &manifest);
+ content.close();
+ zip.addFile(QString::fromLatin1("content.xml"), &content);
+ zip.close();
+ }
+
+ virtual void addFile(const QString &fileName, const QString &mimeType, const QByteArray &bytes)
+ {
+ zip.addFile(fileName, bytes);
+ addFile(fileName, mimeType);
+ }
+
+private:
+ void addFile(const QString &fileName, const QString &mimeType)
+ {
+ manifestWriter.writeEmptyElement(manifestNS, QString::fromLatin1("file-entry"));
+ manifestWriter.writeAttribute(manifestNS, QString::fromLatin1("media-type"), mimeType);
+ manifestWriter.writeAttribute(manifestNS, QString::fromLatin1("full-path"), fileName);
+ }
+
+ QBuffer content;
+ QBuffer manifest;
+ QZipWriter zip;
+ QXmlStreamWriter manifestWriter;
+ QString manifestNS;
+};
+
+static QString bulletChar(QTextListFormat::Style style)
+{
+ switch(style) {
+ case QTextListFormat::ListDisc:
+ return QChar(0x25cf); // bullet character
+ case QTextListFormat::ListCircle:
+ return QChar(0x25cb); // white circle
+ case QTextListFormat::ListSquare:
+ return QChar(0x25a1); // white square
+ case QTextListFormat::ListDecimal:
+ return QString::fromLatin1("1");
+ case QTextListFormat::ListLowerAlpha:
+ return QString::fromLatin1("a");
+ case QTextListFormat::ListUpperAlpha:
+ return QString::fromLatin1("A");
+ default:
+ case QTextListFormat::ListStyleUndefined:
+ return QString();
+ }
+}
+
+void QTextOdfWriter::writeFrame(QXmlStreamWriter &writer, const QTextFrame *frame)
+{
+ Q_ASSERT(frame);
+ const QTextTable *table = qobject_cast<const QTextTable*> (frame);
+
+ if (table) { // Start a table.
+ writer.writeStartElement(tableNS, QString::fromLatin1("table"));
+ writer.writeEmptyElement(tableNS, QString::fromLatin1("table-column"));
+ writer.writeAttribute(tableNS, QString::fromLatin1("number-columns-repeated"), QString::number(table->columns()));
+ } else if (frame->document() && frame->document()->rootFrame() != frame) { // start a section
+ writer.writeStartElement(textNS, QString::fromLatin1("section"));
+ }
+
+ QTextFrame::iterator iterator = frame->begin();
+ QTextFrame *child = 0;
+
+ int tableRow = -1;
+ while (! iterator.atEnd()) {
+ if (iterator.currentFrame() && child != iterator.currentFrame())
+ writeFrame(writer, iterator.currentFrame());
+ else { // no frame, its a block
+ QTextBlock block = iterator.currentBlock();
+ if (table) {
+ QTextTableCell cell = table->cellAt(block.position());
+ if (tableRow < cell.row()) {
+ if (tableRow >= 0)
+ writer.writeEndElement(); // close table row
+ tableRow = cell.row();
+ writer.writeStartElement(tableNS, QString::fromLatin1("table-row"));
+ }
+ writer.writeStartElement(tableNS, QString::fromLatin1("table-cell"));
+ if (cell.columnSpan() > 1)
+ writer.writeAttribute(tableNS, QString::fromLatin1("number-columns-spanned"), QString::number(cell.columnSpan()));
+ if (cell.rowSpan() > 1)
+ writer.writeAttribute(tableNS, QString::fromLatin1("number-rows-spanned"), QString::number(cell.rowSpan()));
+ if (cell.format().isTableCellFormat()) {
+ writer.writeAttribute(tableNS, QString::fromLatin1("style-name"), QString::fromLatin1("T%1").arg(cell.tableCellFormatIndex()));
+ }
+ }
+ writeBlock(writer, block);
+ if (table)
+ writer.writeEndElement(); // table-cell
+ }
+ child = iterator.currentFrame();
+ ++iterator;
+ }
+ if (tableRow >= 0)
+ writer.writeEndElement(); // close table-row
+
+ if (table || (frame->document() && frame->document()->rootFrame() != frame))
+ writer.writeEndElement(); // close table or section element
+}
+
+void QTextOdfWriter::writeBlock(QXmlStreamWriter &writer, const QTextBlock &block)
+{
+ if (block.textList()) { // its a list-item
+ const int listLevel = block.textList()->format().indent();
+ if (m_listStack.isEmpty() || m_listStack.top() != block.textList()) {
+ // not the same list we were in.
+ while (m_listStack.count() >= listLevel && !m_listStack.isEmpty() && m_listStack.top() != block.textList() ) { // we need to close tags
+ m_listStack.pop();
+ writer.writeEndElement(); // list
+ if (m_listStack.count())
+ writer.writeEndElement(); // list-item
+ }
+ while (m_listStack.count() < listLevel) {
+ if (m_listStack.count())
+ writer.writeStartElement(textNS, QString::fromLatin1("list-item"));
+ writer.writeStartElement(textNS, QString::fromLatin1("list"));
+ if (m_listStack.count() == listLevel - 1) {
+ m_listStack.push(block.textList());
+ writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("L%1")
+ .arg(block.textList()->formatIndex()));
+ }
+ else {
+ m_listStack.push(0);
+ }
+ }
+ }
+ writer.writeStartElement(textNS, QString::fromLatin1("list-item"));
+ }
+ else {
+ while (! m_listStack.isEmpty()) {
+ m_listStack.pop();
+ writer.writeEndElement(); // list
+ if (m_listStack.count())
+ writer.writeEndElement(); // list-item
+ }
+ }
+
+ if (block.length() == 1) { // only a linefeed
+ writer.writeEmptyElement(textNS, QString::fromLatin1("p"));
+ writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("p%1")
+ .arg(block.blockFormatIndex()));
+ if (block.textList())
+ writer.writeEndElement(); // numbered-paragraph
+ return;
+ }
+ writer.writeStartElement(textNS, QString::fromLatin1("p"));
+ writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("p%1")
+ .arg(block.blockFormatIndex()));
+ for (QTextBlock::Iterator frag= block.begin(); !frag.atEnd(); frag++) {
+ writer.writeCharacters(QString()); // Trick to make sure that the span gets no linefeed in front of it.
+ writer.writeStartElement(textNS, QString::fromLatin1("span"));
+
+ QString fragmentText = frag.fragment().text();
+ if (fragmentText.length() == 1 && fragmentText[0] == 0xFFFC) { // its an inline character.
+ writeInlineCharacter(writer, frag.fragment());
+ writer.writeEndElement(); // span
+ continue;
+ }
+
+ writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("c%1")
+ .arg(frag.fragment().charFormatIndex()));
+ bool escapeNextSpace = true;
+ int precedingSpaces = 0, precedingTabs = 0;
+ int exportedIndex = 0;
+ for (int i=0; i <= fragmentText.count(); ++i) {
+ bool isTab = false, isSpace = false;
+ if (i < fragmentText.count()) {
+ QChar character = fragmentText[i];
+ isTab = character.unicode() == '\t';
+ isSpace = character.unicode() == ' ';
+ if (character.unicode() == 0x2028) { // soft-return
+ writer.writeCharacters(fragmentText.mid(exportedIndex, i));
+ writer.writeEmptyElement(textNS, QString::fromLatin1("line-break"));
+ exportedIndex = i+1;
+ continue;
+ }
+ if (isSpace) {
+ ++precedingSpaces;
+ escapeNextSpace = true;
+ }
+ else if (isTab) {
+ precedingTabs++;
+ }
+ }
+ // find more than one space. -> <text:s text:c="2" />
+ if (!isSpace && escapeNextSpace && precedingSpaces > 1) {
+ const bool startParag = exportedIndex == 0 && i == precedingSpaces;
+ if (!startParag)
+ writer.writeCharacters(fragmentText.mid(exportedIndex, i - precedingSpaces + 1 - exportedIndex));
+ writer.writeEmptyElement(textNS, QString::fromLatin1("s"));
+ const int count = precedingSpaces - (startParag?0:1);
+ if (count > 1)
+ writer.writeAttribute(textNS, QString::fromLatin1("c"), QString::number(count));
+ precedingSpaces = 0;
+ exportedIndex = i;
+ }
+ // find tabs. -> <text:tab text:tab-ref="3" /> or <text:tab/>
+ if (!isTab && precedingTabs) {
+ writer.writeCharacters(fragmentText.mid(exportedIndex, i - precedingTabs - exportedIndex));
+ writer.writeEmptyElement(textNS, QString::fromLatin1("tab"));
+ if (precedingTabs > 1)
+ writer.writeAttribute(textNS, QString::fromLatin1("tab-ref"), QString::number(precedingTabs));
+ precedingTabs = 0;
+ exportedIndex = i;
+ }
+ if (!isSpace && !isTab)
+ precedingSpaces = 0;
+ }
+
+ writer.writeCharacters(fragmentText.mid(exportedIndex));
+ writer.writeEndElement(); // span
+ }
+ writer.writeCharacters(QString()); // Trick to make sure that the span gets no linefeed behind it.
+ writer.writeEndElement(); // p
+ if (block.textList())
+ writer.writeEndElement(); // list-item
+}
+
+void QTextOdfWriter::writeInlineCharacter(QXmlStreamWriter &writer, const QTextFragment &fragment) const
+{
+ writer.writeStartElement(drawNS, QString::fromLatin1("frame"));
+ if (m_strategy == 0) {
+ // don't do anything.
+ }
+ else if (fragment.charFormat().isImageFormat()) {
+ QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
+ writer.writeAttribute(drawNS, QString::fromLatin1("name"), imageFormat.name());
+
+ // vvv Copy pasted mostly from Qt =================
+ QImage image;
+ QString name = imageFormat.name();
+ if (name.startsWith(QLatin1String(":/"))) // auto-detect resources
+ name.prepend(QLatin1String("qrc"));
+ QUrl url = QUrl::fromEncoded(name.toUtf8());
+ const QVariant data = m_document->resource(QTextDocument::ImageResource, url);
+ if (data.type() == QVariant::Image) {
+ image = qvariant_cast<QImage>(data);
+ } else if (data.type() == QVariant::ByteArray) {
+ image.loadFromData(data.toByteArray());
+ }
+
+ if (image.isNull()) {
+ QString context;
+ if (QTextImageHandler::externalLoader)
+ image = QTextImageHandler::externalLoader(name, context);
+
+ if (image.isNull()) { // try direct loading
+ name = imageFormat.name(); // remove qrc:/ prefix again
+ image.load(name);
+ }
+ }
+
+ // ^^^ Copy pasted mostly from Qt =================
+ if (! image.isNull()) {
+ QBuffer imageBytes;
+ QImageWriter imageWriter(&imageBytes, "png");
+ imageWriter.write(image);
+ QString filename = m_strategy->createUniqueImageName();
+ m_strategy->addFile(filename, QString::fromLatin1("image/png"), imageBytes.data());
+
+ // get the width/height from the format.
+ qreal width = (imageFormat.hasProperty(QTextFormat::ImageWidth)) ? imageFormat.width() : image.width();
+ writer.writeAttribute(svgNS, QString::fromLatin1("width"), pixelToPoint(width));
+ qreal height = (imageFormat.hasProperty(QTextFormat::ImageHeight)) ? imageFormat.height() : image.height();
+ writer.writeAttribute(svgNS, QString::fromLatin1("height"), pixelToPoint(height));
+
+ writer.writeStartElement(drawNS, QString::fromLatin1("image"));
+ writer.writeAttribute(xlinkNS, QString::fromLatin1("href"), filename);
+ writer.writeEndElement(); // image
+ }
+ }
+
+ writer.writeEndElement(); // frame
+}
+
+void QTextOdfWriter::writeFormats(QXmlStreamWriter &writer, QSet<int> formats) const
+{
+ writer.writeStartElement(officeNS, QString::fromLatin1("automatic-styles"));
+ QVector<QTextFormat> allStyles = m_document->allFormats();
+ QSetIterator<int> formatId(formats);
+ while(formatId.hasNext()) {
+ int formatIndex = formatId.next();
+ QTextFormat textFormat = allStyles.at(formatIndex);
+ switch (textFormat.type()) {
+ case QTextFormat::CharFormat:
+ if (textFormat.isTableCellFormat())
+ writeTableCellFormat(writer, textFormat.toTableCellFormat(), formatIndex);
+ else
+ writeCharacterFormat(writer, textFormat.toCharFormat(), formatIndex);
+ break;
+ case QTextFormat::BlockFormat:
+ writeBlockFormat(writer, textFormat.toBlockFormat(), formatIndex);
+ break;
+ case QTextFormat::ListFormat:
+ writeListFormat(writer, textFormat.toListFormat(), formatIndex);
+ break;
+ case QTextFormat::FrameFormat:
+ writeFrameFormat(writer, textFormat.toFrameFormat(), formatIndex);
+ break;
+ case QTextFormat::TableFormat:
+ ;break;
+ }
+ }
+
+ writer.writeEndElement(); // automatic-styles
+}
+
+void QTextOdfWriter::writeBlockFormat(QXmlStreamWriter &writer, QTextBlockFormat format, int formatIndex) const
+{
+ writer.writeStartElement(styleNS, QString::fromLatin1("style"));
+ writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("p%1").arg(formatIndex));
+ writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("paragraph"));
+ writer.writeStartElement(styleNS, QString::fromLatin1("paragraph-properties"));
+
+ if (format.hasProperty(QTextFormat::BlockAlignment)) {
+ QString value;
+ if (format.alignment() == Qt::AlignLeading)
+ value = QString::fromLatin1("start");
+ else if (format.alignment() == Qt::AlignTrailing)
+ value = QString::fromLatin1("end");
+ else if (format.alignment() == (Qt::AlignLeft | Qt::AlignAbsolute))
+ value = QString::fromLatin1("left");
+ else if (format.alignment() == (Qt::AlignRight | Qt::AlignAbsolute))
+ value = QString::fromLatin1("right");
+ else if (format.alignment() == Qt::AlignHCenter)
+ value = QString::fromLatin1("center");
+ else if (format.alignment() == Qt::AlignJustify)
+ value = QString::fromLatin1("justify");
+ else
+ qWarning() << "QTextOdfWriter: unsupported paragraph alignment; " << format.alignment();
+ if (! value.isNull())
+ writer.writeAttribute(foNS, QString::fromLatin1("text-align"), value);
+ }
+
+ if (format.hasProperty(QTextFormat::BlockTopMargin))
+ writer.writeAttribute(foNS, QString::fromLatin1("margin-top"), pixelToPoint(qMax(qreal(0.), format.topMargin())) );
+ if (format.hasProperty(QTextFormat::BlockBottomMargin))
+ writer.writeAttribute(foNS, QString::fromLatin1("margin-bottom"), pixelToPoint(qMax(qreal(0.), format.bottomMargin())) );
+ if (format.hasProperty(QTextFormat::BlockLeftMargin) || format.hasProperty(QTextFormat::BlockIndent))
+ writer.writeAttribute(foNS, QString::fromLatin1("margin-left"), pixelToPoint(qMax(qreal(0.),
+ format.leftMargin() + format.indent())));
+ if (format.hasProperty(QTextFormat::BlockRightMargin))
+ writer.writeAttribute(foNS, QString::fromLatin1("margin-right"), pixelToPoint(qMax(qreal(0.), format.rightMargin())) );
+ if (format.hasProperty(QTextFormat::TextIndent))
+ writer.writeAttribute(foNS, QString::fromLatin1("text-indent"), QString::number(format.textIndent()));
+ if (format.hasProperty(QTextFormat::PageBreakPolicy)) {
+ if (format.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
+ writer.writeAttribute(foNS, QString::fromLatin1("break-before"), QString::fromLatin1("page"));
+ if (format.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
+ writer.writeAttribute(foNS, QString::fromLatin1("break-after"), QString::fromLatin1("page"));
+ }
+ if (format.hasProperty(QTextFormat::BlockNonBreakableLines))
+ writer.writeAttribute(foNS, QString::fromLatin1("keep-together"),
+ format.nonBreakableLines() ? QString::fromLatin1("true") : QString::fromLatin1("false"));
+ if (format.hasProperty(QTextFormat::TabPositions)) {
+ QList<QTextOption::Tab> tabs = format.tabPositions();
+ writer.writeStartElement(styleNS, QString::fromLatin1("style-tab-stops"));
+ QList<QTextOption::Tab>::Iterator iterator = tabs.begin();
+ while(iterator != tabs.end()) {
+ writer.writeEmptyElement(styleNS, QString::fromLatin1("style-tab-stop"));
+ writer.writeAttribute(styleNS, QString::fromLatin1("position"), pixelToPoint(iterator->position) );
+ QString type;
+ switch(iterator->type) {
+ case QTextOption::DelimiterTab: type = QString::fromLatin1("char"); break;
+ case QTextOption::LeftTab: type = QString::fromLatin1("left"); break;
+ case QTextOption::RightTab: type = QString::fromLatin1("right"); break;
+ case QTextOption::CenterTab: type = QString::fromLatin1("center"); break;
+ }
+ writer.writeAttribute(styleNS, QString::fromLatin1("type"), type);
+ if (iterator->delimiter != 0)
+ writer.writeAttribute(styleNS, QString::fromLatin1("char"), iterator->delimiter);
+ ++iterator;
+ }
+
+ writer.writeEndElement(); // style-tab-stops
+ }
+
+ writer.writeEndElement(); // paragraph-properties
+ writer.writeEndElement(); // style
+}
+
+void QTextOdfWriter::writeCharacterFormat(QXmlStreamWriter &writer, QTextCharFormat format, int formatIndex) const
+{
+ writer.writeStartElement(styleNS, QString::fromLatin1("style"));
+ writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("c%1").arg(formatIndex));
+ writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("text"));
+ writer.writeEmptyElement(styleNS, QString::fromLatin1("text-properties"));
+ if (format.fontItalic())
+ writer.writeAttribute(foNS, QString::fromLatin1("font-style"), QString::fromLatin1("italic"));
+ if (format.hasProperty(QTextFormat::FontWeight) && format.fontWeight() != QFont::Normal) {
+ QString value;
+ if (format.fontWeight() == QFont::Bold)
+ value = QString::fromLatin1("bold");
+ else
+ value = QString::number(format.fontWeight() * 10);
+ writer.writeAttribute(foNS, QString::fromLatin1("font-weight"), value);
+ }
+ if (format.hasProperty(QTextFormat::FontFamily))
+ writer.writeAttribute(foNS, QString::fromLatin1("font-family"), format.fontFamily());
+ else
+ writer.writeAttribute(foNS, QString::fromLatin1("font-family"), QString::fromLatin1("Sans")); // Qt default
+ if (format.hasProperty(QTextFormat::FontPointSize))
+ writer.writeAttribute(foNS, QString::fromLatin1("font-size"), QString::fromLatin1("%1pt").arg(format.fontPointSize()));
+ if (format.hasProperty(QTextFormat::FontCapitalization)) {
+ switch(format.fontCapitalization()) {
+ case QFont::MixedCase:
+ writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("none")); break;
+ case QFont::AllUppercase:
+ writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("uppercase")); break;
+ case QFont::AllLowercase:
+ writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("lowercase")); break;
+ case QFont::Capitalize:
+ writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("capitalize")); break;
+ case QFont::SmallCaps:
+ writer.writeAttribute(foNS, QString::fromLatin1("font-variant"), QString::fromLatin1("small-caps")); break;
+ }
+ }
+ if (format.hasProperty(QTextFormat::FontLetterSpacing))
+ writer.writeAttribute(foNS, QString::fromLatin1("letter-spacing"), pixelToPoint(format.fontLetterSpacing()) );
+ if (format.hasProperty(QTextFormat::FontWordSpacing))
+ writer.writeAttribute(foNS, QString::fromLatin1("letter-spacing"), pixelToPoint(format.fontWordSpacing()) );
+ if (format.hasProperty(QTextFormat::FontUnderline))
+ writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-type"),
+ format.fontUnderline() ? QString::fromLatin1("single") : QString::fromLatin1("none"));
+ if (format.hasProperty(QTextFormat::FontOverline)) {
+ // bool fontOverline () const TODO
+ }
+ if (format.hasProperty(QTextFormat::FontStrikeOut))
+ writer.writeAttribute(styleNS,QString::fromLatin1( "text-line-through-type"),
+ format.fontStrikeOut() ? QString::fromLatin1("single") : QString::fromLatin1("none"));
+ if (format.hasProperty(QTextFormat::TextUnderlineColor))
+ writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-color"), format.underlineColor().name());
+ if (format.hasProperty(QTextFormat::FontFixedPitch)) {
+ // bool fontFixedPitch () const TODO
+ }
+ if (format.hasProperty(QTextFormat::TextUnderlineStyle)) {
+ QString value;
+ switch (format.underlineStyle()) {
+ case QTextCharFormat::NoUnderline: value = QString::fromLatin1("none"); break;
+ case QTextCharFormat::SingleUnderline: value = QString::fromLatin1("solid"); break;
+ case QTextCharFormat::DashUnderline: value = QString::fromLatin1("dash"); break;
+ case QTextCharFormat::DotLine: value = QString::fromLatin1("dotted"); break;
+ case QTextCharFormat::DashDotLine: value = QString::fromLatin1("dash-dot"); break;
+ case QTextCharFormat::DashDotDotLine: value = QString::fromLatin1("dot-dot-dash"); break;
+ case QTextCharFormat::WaveUnderline: value = QString::fromLatin1("wave"); break;
+ case QTextCharFormat::SpellCheckUnderline: value = QString::fromLatin1("none"); break;
+ }
+ writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-style"), value);
+ }
+ if (format.hasProperty(QTextFormat::TextVerticalAlignment)) {
+ QString value;
+ switch (format.verticalAlignment()) {
+ case QTextCharFormat::AlignMiddle:
+ case QTextCharFormat::AlignNormal: value = QString::fromLatin1("0%"); break;
+ case QTextCharFormat::AlignSuperScript: value = QString::fromLatin1("super"); break;
+ case QTextCharFormat::AlignSubScript: value = QString::fromLatin1("sub"); break;
+ case QTextCharFormat::AlignTop: value = QString::fromLatin1("100%"); break;
+ case QTextCharFormat::AlignBottom : value = QString::fromLatin1("-100%"); break;
+ }
+ writer.writeAttribute(styleNS, QString::fromLatin1("text-position"), value);
+ }
+ if (format.hasProperty(QTextFormat::TextOutline))
+ writer.writeAttribute(styleNS, QString::fromLatin1("text-outline"), QString::fromLatin1("true"));
+ if (format.hasProperty(QTextFormat::TextToolTip)) {
+ // QString toolTip () const TODO
+ }
+ if (format.hasProperty(QTextFormat::IsAnchor)) {
+ // bool isAnchor () const TODO
+ }
+ if (format.hasProperty(QTextFormat::AnchorHref)) {
+ // QString anchorHref () const TODO
+ }
+ if (format.hasProperty(QTextFormat::AnchorName)) {
+ // QString anchorName () const TODO
+ }
+ if (format.hasProperty(QTextFormat::ForegroundBrush)) {
+ QBrush brush = format.foreground();
+ // TODO
+ writer.writeAttribute(foNS, QString::fromLatin1("color"), brush.color().name());
+ }
+
+ writer.writeEndElement(); // style
+}
+
+void QTextOdfWriter::writeListFormat(QXmlStreamWriter &writer, QTextListFormat format, int formatIndex) const
+{
+ writer.writeStartElement(textNS, QString::fromLatin1("list-style"));
+ writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("L%1").arg(formatIndex));
+
+ QTextListFormat::Style style = format.style();
+ if (style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha
+ || style == QTextListFormat::ListUpperAlpha) {
+ writer.writeStartElement(textNS, QString::fromLatin1("list-level-style-number"));
+ writer.writeAttribute(styleNS, QString::fromLatin1("num-format"), bulletChar(style));
+ writer.writeAttribute(styleNS, QString::fromLatin1("num-suffix"), QString::fromLatin1("."));
+ } else {
+ writer.writeStartElement(textNS, QString::fromLatin1("list-level-style-bullet"));
+ writer.writeAttribute(textNS, QString::fromLatin1("bullet-char"), bulletChar(style));
+ }
+
+ writer.writeAttribute(textNS, QString::fromLatin1("level"), QString::number(format.indent()));
+ writer.writeEmptyElement(styleNS, QString::fromLatin1("list-level-properties"));
+ writer.writeAttribute(foNS, QString::fromLatin1("text-align"), QString::fromLatin1("start"));
+ QString spacing = QString::fromLatin1("%1mm").arg(format.indent() * 8);
+ writer.writeAttribute(textNS, QString::fromLatin1("space-before"), spacing);
+ //writer.writeAttribute(textNS, QString::fromLatin1("min-label-width"), spacing);
+
+ writer.writeEndElement(); // list-level-style-*
+ writer.writeEndElement(); // list-style
+}
+
+void QTextOdfWriter::writeFrameFormat(QXmlStreamWriter &writer, QTextFrameFormat format, int formatIndex) const
+{
+ writer.writeStartElement(styleNS, QString::fromLatin1("style"));
+ writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("s%1").arg(formatIndex));
+ writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("section"));
+ writer.writeEmptyElement(styleNS, QString::fromLatin1("section-properties"));
+ if (format.hasProperty(QTextFormat::BlockTopMargin))
+ writer.writeAttribute(foNS, QString::fromLatin1("margin-top"), pixelToPoint(qMax(qreal(0.), format.topMargin())) );
+ if (format.hasProperty(QTextFormat::BlockBottomMargin))
+ writer.writeAttribute(foNS, QString::fromLatin1("margin-bottom"), pixelToPoint(qMax(qreal(0.), format.bottomMargin())) );
+ if (format.hasProperty(QTextFormat::BlockLeftMargin))
+ writer.writeAttribute(foNS, QString::fromLatin1("margin-left"), pixelToPoint(qMax(qreal(0.), format.leftMargin())) );
+ if (format.hasProperty(QTextFormat::BlockRightMargin))
+ writer.writeAttribute(foNS, QString::fromLatin1("margin-right"), pixelToPoint(qMax(qreal(0.), format.rightMargin())) );
+
+ writer.writeEndElement(); // style
+
+// TODO consider putting the following properties in a qt-namespace.
+// Position position () const
+// qreal border () const
+// QBrush borderBrush () const
+// BorderStyle borderStyle () const
+// qreal padding () const
+// QTextLength width () const
+// QTextLength height () const
+// PageBreakFlags pageBreakPolicy () const
+}
+
+void QTextOdfWriter::writeTableCellFormat(QXmlStreamWriter &writer, QTextTableCellFormat format, int formatIndex) const
+{
+ writer.writeStartElement(styleNS, QString::fromLatin1("style"));
+ writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("T%1").arg(formatIndex));
+ writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("table"));
+ writer.writeEmptyElement(styleNS, QString::fromLatin1("table-properties"));
+
+
+ qreal padding = format.topPadding();
+ if (padding > 0 && padding == format.bottomPadding()
+ && padding == format.leftPadding() && padding == format.rightPadding()) {
+ writer.writeAttribute(foNS, QString::fromLatin1("padding"), pixelToPoint(padding));
+ }
+ else {
+ if (padding > 0)
+ writer.writeAttribute(foNS, QString::fromLatin1("padding-top"), pixelToPoint(padding));
+ if (format.bottomPadding() > 0)
+ writer.writeAttribute(foNS, QString::fromLatin1("padding-top"), pixelToPoint(format.bottomPadding()));
+ if (format.leftPadding() > 0)
+ writer.writeAttribute(foNS, QString::fromLatin1("padding-top"), pixelToPoint(format.leftPadding()));
+ if (format.rightPadding() > 0)
+ writer.writeAttribute(foNS, QString::fromLatin1("padding-top"), pixelToPoint(format.rightPadding()));
+ }
+
+ if (format.hasProperty(QTextFormat::TextVerticalAlignment)) {
+ QString pos;
+ switch (format.verticalAlignment()) {
+ case QTextCharFormat::AlignMiddle:
+ pos = QString::fromLatin1("middle"); break;
+ case QTextCharFormat::AlignTop:
+ pos = QString::fromLatin1("top"); break;
+ case QTextCharFormat::AlignBottom:
+ pos = QString::fromLatin1("bottom"); break;
+ default:
+ pos = QString::fromLatin1("automatic"); break;
+ }
+ writer.writeAttribute(styleNS, QString::fromLatin1("vertical-align"), pos);
+ }
+
+ // TODO
+ // ODF just search for style-table-cell-properties-attlist)
+ // QTextFormat::BackgroundImageUrl
+ // format.background
+ // QTextFormat::FrameBorder
+
+ writer.writeEndElement(); // style
+}
+
+///////////////////////
+
+QTextOdfWriter::QTextOdfWriter(const QTextDocument &document, QIODevice *device)
+ : officeNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:office:1.0")),
+ textNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:text:1.0")),
+ styleNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:style:1.0")),
+ foNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0")),
+ tableNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:table:1.0")),
+ drawNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0")),
+ xlinkNS (QLatin1String("http://www.w3.org/1999/xlink")),
+ svgNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0")),
+ m_document(&document),
+ m_device(device),
+ m_strategy(0),
+ m_codec(0),
+ m_createArchive(true)
+{
+}
+
+bool QTextOdfWriter::writeAll()
+{
+ if (m_createArchive)
+ m_strategy = new QZipStreamStrategy(m_device);
+ else
+ m_strategy = new QXmlStreamStrategy(m_device);
+
+ if (!m_device->isWritable() && ! m_device->open(QIODevice::WriteOnly)) {
+ qWarning() << "QTextOdfWriter::writeAll: the device can not be opened for writing";
+ return false;
+ }
+ QXmlStreamWriter writer(m_strategy->contentStream);
+#ifndef QT_NO_TEXTCODEC
+ if (m_codec)
+ writer.setCodec(m_codec);
+#endif
+ // prettyfy
+ writer.setAutoFormatting(true);
+ writer.setAutoFormattingIndent(2);
+
+ writer.writeNamespace(officeNS, QString::fromLatin1("office"));
+ writer.writeNamespace(textNS, QString::fromLatin1("text"));
+ writer.writeNamespace(styleNS, QString::fromLatin1("style"));
+ writer.writeNamespace(foNS, QString::fromLatin1("fo"));
+ writer.writeNamespace(tableNS, QString::fromLatin1("table"));
+ writer.writeNamespace(drawNS, QString::fromLatin1("draw"));
+ writer.writeNamespace(xlinkNS, QString::fromLatin1("xlink"));
+ writer.writeNamespace(svgNS, QString::fromLatin1("svg"));
+ writer.writeStartDocument();
+ writer.writeStartElement(officeNS, QString::fromLatin1("document-content"));
+
+ // add fragments. (for character formats)
+ QTextDocumentPrivate::FragmentIterator fragIt = m_document->docHandle()->begin();
+ QSet<int> formats;
+ while (fragIt != m_document->docHandle()->end()) {
+ const QTextFragmentData * const frag = fragIt.value();
+ formats << frag->format;
+ ++fragIt;
+ }
+
+ // add blocks (for blockFormats)
+ QTextDocumentPrivate::BlockMap &blocks = m_document->docHandle()->blockMap();
+ QTextDocumentPrivate::BlockMap::Iterator blockIt = blocks.begin();
+ while (blockIt != blocks.end()) {
+ const QTextBlockData * const block = blockIt.value();
+ formats << block->format;
+ ++blockIt;
+ }
+
+ // add objects for lists, frames and tables
+ QVector<QTextFormat> allFormats = m_document->allFormats();
+ QList<int> copy = formats.toList();
+ for (QList<int>::Iterator iter = copy.begin(); iter != copy.end(); ++iter) {
+ QTextObject *object = m_document->objectForFormat(allFormats[*iter]);
+ if (object)
+ formats << object->formatIndex();
+ }
+
+ writeFormats(writer, formats);
+
+ writer.writeStartElement(officeNS, QString::fromLatin1("body"));
+ writer.writeStartElement(officeNS, QString::fromLatin1("text"));
+ QTextFrame *rootFrame = m_document->rootFrame();
+ writeFrame(writer, rootFrame);
+ writer.writeEndElement(); // text
+ writer.writeEndElement(); // body
+ writer.writeEndElement(); // document-content
+ writer.writeEndDocument();
+ delete m_strategy;
+ m_strategy = 0;
+
+ return true;
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_TEXTODFWRITER
diff --git a/src/gui/text/qtextodfwriter_p.h b/src/gui/text/qtextodfwriter_p.h
new file mode 100644
index 0000000000..88e6b460d4
--- /dev/null
+++ b/src/gui/text/qtextodfwriter_p.h
@@ -0,0 +1,115 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTODFWRITER_H
+#define QTEXTODFWRITER_H
+#ifndef QT_NO_TEXTODFWRITER
+
+//
+// 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 <QtCore/QXmlStreamWriter>
+#include <QtCore/qset.h>
+#include <QtCore/qstack.h>
+
+#include "qtextdocument_p.h"
+#include "qtextdocumentwriter.h"
+
+QT_BEGIN_NAMESPACE
+
+class QTextDocumentPrivate;
+class QTextCursor;
+class QTextBlock;
+class QIODevice;
+class QXmlStreamWriter;
+class QTextOdfWriterPrivate;
+class QTextBlockFormat;
+class QTextCharFormat;
+class QTextListFormat;
+class QTextFrameFormat;
+class QTextTableCellFormat;
+class QTextFrame;
+class QTextFragment;
+class QOutputStrategy;
+
+class Q_AUTOTEST_EXPORT QTextOdfWriter {
+public:
+ QTextOdfWriter(const QTextDocument &document, QIODevice *device);
+ bool writeAll();
+
+ void setCodec(QTextCodec *codec) { m_codec = codec; }
+ void setCreateArchive(bool on) { m_createArchive = on; }
+ bool createArchive() const { return m_createArchive; }
+
+ void writeBlock(QXmlStreamWriter &writer, const QTextBlock &block);
+ void writeFormats(QXmlStreamWriter &writer, QSet<int> formatIds) const;
+ void writeBlockFormat(QXmlStreamWriter &writer, QTextBlockFormat format, int formatIndex) const;
+ void writeCharacterFormat(QXmlStreamWriter &writer, QTextCharFormat format, int formatIndex) const;
+ void writeListFormat(QXmlStreamWriter &writer, QTextListFormat format, int formatIndex) const;
+ void writeFrameFormat(QXmlStreamWriter &writer, QTextFrameFormat format, int formatIndex) const;
+ void writeTableCellFormat(QXmlStreamWriter &writer, QTextTableCellFormat format, int formatIndex) const;
+ void writeFrame(QXmlStreamWriter &writer, const QTextFrame *frame);
+ void writeInlineCharacter(QXmlStreamWriter &writer, const QTextFragment &fragment) const;
+
+ const QString officeNS, textNS, styleNS, foNS, tableNS, drawNS, xlinkNS, svgNS;
+private:
+ const QTextDocument *m_document;
+ QIODevice *m_device;
+
+ QOutputStrategy *m_strategy;
+ QTextCodec *m_codec;
+ bool m_createArchive;
+
+ QStack<QTextList *> m_listStack;
+};
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_TEXTODFWRITER
+#endif // QTEXTODFWRITER_H
diff --git a/src/gui/text/qtextoption.cpp b/src/gui/text/qtextoption.cpp
new file mode 100644
index 0000000000..e1b98445a3
--- /dev/null
+++ b/src/gui/text/qtextoption.cpp
@@ -0,0 +1,414 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtextoption.h"
+#include "qapplication.h"
+#include "qlist.h"
+
+QT_BEGIN_NAMESPACE
+
+struct QTextOptionPrivate
+{
+ QList<QTextOption::Tab> tabStops;
+};
+
+/*!
+ Constructs a text option with default properties for text.
+*/
+QTextOption::QTextOption()
+ : align(Qt::AlignLeft),
+ wordWrap(QTextOption::WordWrap),
+ design(false),
+ unused(0),
+ f(0),
+ tab(-1),
+ d(0)
+{
+ direction = QApplication::layoutDirection();
+}
+
+/*!
+ Constructs a text option with the given \a alignment for text.
+*/
+QTextOption::QTextOption(Qt::Alignment alignment)
+ : align(alignment),
+ wordWrap(QTextOption::WordWrap),
+ design(false),
+ unused(0),
+ f(0),
+ tab(-1),
+ d(0)
+{
+ direction = QApplication::layoutDirection();
+}
+
+/*!
+ Destroys the text option.
+*/
+QTextOption::~QTextOption()
+{
+ delete d;
+}
+
+/*!
+ \fn QTextOption::QTextOption(const QTextOption &other)
+
+ Construct a copy of the \a other text option.
+*/
+QTextOption::QTextOption(const QTextOption &o)
+ : align(o.align),
+ wordWrap(o.wordWrap),
+ design(o.design),
+ direction(o.direction),
+ unused(o.unused),
+ f(o.f),
+ tab(o.tab),
+ d(0)
+{
+ if (o.d)
+ d = new QTextOptionPrivate(*o.d);
+}
+
+/*!
+ \fn QTextOption &QTextOption::operator=(const QTextOption &other)
+
+ Returns true if the text option is the same as the \a other text option;
+ otherwise returns false.
+*/
+QTextOption &QTextOption::operator=(const QTextOption &o)
+{
+ if (this == &o)
+ return *this;
+ delete d; d = 0;
+ align = o.align;
+ wordWrap = o.wordWrap;
+ design = o.design;
+ direction = o.direction;
+ unused = o.unused;
+ f = o.f;
+ tab = o.tab;
+ if (o.d)
+ d = new QTextOptionPrivate(*o.d);
+ return *this;
+}
+
+/*!
+ Sets the tab positions for the text layout to those specified by
+ \a tabStops.
+
+ \sa tabArray(), setTabStop(), setTabs()
+*/
+void QTextOption::setTabArray(QList<qreal> tabStops)
+{
+ if (!d)
+ d = new QTextOptionPrivate;
+ QList<QTextOption::Tab> tabs;
+ QTextOption::Tab tab;
+ foreach (qreal pos, tabStops) {
+ tab.position = pos;
+ tabs.append(tab);
+ }
+ d->tabStops = tabs;
+}
+
+/*!
+ \since 4.4
+ Sets the tab positions for the text layout to those specified by
+ \a tabStops.
+
+ \sa tabStops()
+*/
+void QTextOption::setTabs(QList<QTextOption::Tab> tabStops)
+{
+ if (!d)
+ d = new QTextOptionPrivate;
+ d->tabStops = tabStops;
+}
+
+/*!
+ Returns a list of tab positions defined for the text layout.
+
+ \sa setTabArray(), tabStop()
+*/
+QList<qreal> QTextOption::tabArray() const
+{
+ if (!d)
+ return QList<qreal>();
+
+ QList<qreal> answer;
+ QList<QTextOption::Tab>::ConstIterator iter = d->tabStops.constBegin();
+ while(iter != d->tabStops.constEnd()) {
+ answer.append( (*iter).position);
+ ++iter;
+ }
+ return answer;
+}
+
+
+QList<QTextOption::Tab> QTextOption::tabs() const
+{
+ if (!d)
+ return QList<QTextOption::Tab>();
+ return d->tabStops;
+}
+
+/*!
+ \class QTextOption
+ \reentrant
+
+ \brief The QTextOption class provides a description of general rich text
+ properties.
+
+ \ingroup text
+
+ QTextOption is used to encapsulate common rich text properties in a single
+ object. It contains information about text alignment, layout direction,
+ word wrapping, and other standard properties associated with text rendering
+ and layout.
+
+ \sa QTextEdit, QTextDocument, QTextCursor
+*/
+
+/*!
+ \enum QTextOption::WrapMode
+
+ This enum describes how text is wrapped in a document.
+
+ \value NoWrap Text is not wrapped at all.
+ \value WordWrap Text is wrapped at word boundaries.
+ \value ManualWrap Same as QTextOption::NoWrap
+ \value WrapAnywhere Text can be wrapped at any point on a line, even if
+ it occurs in the middle of a word.
+ \value WrapAtWordBoundaryOrAnywhere If possible, wrapping occurs at a word
+ boundary; otherwise it will occur at the appropriate
+ point on the line, even in the middle of a word.
+*/
+
+/*!
+ \fn void QTextOption::setUseDesignMetrics(bool enable)
+
+ If \a enable is true then the layout will use design metrics;
+ otherwise it will use the metrics of the paint device (which is
+ the default behavior).
+
+ \sa useDesignMetrics()
+*/
+
+/*!
+ \fn bool QTextOption::useDesignMetrics() const
+
+ Returns true if the layout uses design rather than device metrics;
+ otherwise returns false.
+
+ \sa setUseDesignMetrics()
+*/
+
+/*!
+ \fn Qt::Alignment QTextOption::alignment() const
+
+ Returns the text alignment defined by the option.
+
+ \sa setAlignment()
+*/
+
+/*!
+ \fn void QTextOption::setAlignment(Qt::Alignment alignment);
+
+ Sets the option's text alignment to the specified \a alignment.
+
+ \sa alignment()
+*/
+
+/*!
+ \fn Qt::LayoutDirection QTextOption::textDirection() const
+
+ Returns the direction of the text layout defined by the option.
+
+ \sa setTextDirection()
+*/
+
+/*!
+ \fn void QTextOption::setTextDirection(Qt::LayoutDirection direction)
+
+ Sets the direction of the text layout defined by the option to the
+ given \a direction.
+
+ \sa textDirection()
+*/
+
+/*!
+ \fn WrapMode QTextOption::wrapMode() const
+
+ Returns the text wrap mode defined by the option.
+
+ \sa setWrapMode()
+*/
+
+/*!
+ \fn void QTextOption::setWrapMode(WrapMode mode)
+
+ Sets the option's text wrap mode to the given \a mode.
+*/
+
+/*!
+ \enum QTextOption::Flag
+
+ \value IncludeTrailingSpaces When this option is set, QTextLine::naturalTextWidth() and naturalTextRect() will
+ return a value that includes the width of trailing spaces in the text; otherwise
+ this width is excluded.
+ \value ShowTabsAndSpaces Visualize spaces with little dots, and tabs with little arrows.
+ \value ShowLineAndParagraphSeparators Visualize line and paragraph separators with appropriate symbol characters.
+ \value AddSpaceForLineAndParagraphSeparators While determining the line-break positions take into account the
+ space added for drawing a separator character.
+ \value SuppressColors Suppress all color changes in the character formats (except the main selection).
+*/
+
+/*!
+ \fn Flags QTextOption::flags() const
+
+ Returns the flags associated with the option.
+
+ \sa setFlags()
+*/
+
+/*!
+ \fn void QTextOption::setFlags(Flags flags)
+
+ Sets the flags associated with the option to the given \a flags.
+
+ \sa flags()
+*/
+
+/*!
+ \fn qreal QTextOption::tabStop() const
+
+ Returns the distance in device units between tab stops.
+ Convenient function for the above method
+
+ \sa setTabStop(), tabArray(), setTabs(), tabs()
+*/
+
+/*!
+ \fn void QTextOption::setTabStop(qreal tabStop)
+
+ Sets the default distance in device units between tab stops to the value specified
+ by \a tabStop.
+
+ \sa tabStop(), setTabArray(), setTabs(), tabs()
+*/
+
+/*!
+ \enum QTextOption::TabType
+ \since 4.4
+
+ This enum holds the different types of tabulator
+
+ \value LeftTab, A left-tab
+ \value RightTab, A right-tab
+ \value CenterTab, A centered-tab
+ \value DelimiterTab A tab stopping at a certain delimiter-character
+*/
+
+/*!
+ \class QTextOption::Tab
+ \since 4.4
+ Each tab definition is represented by this struct.
+*/
+
+/*!
+ \variable Tab::position
+ Distance from the start of the paragraph.
+ The position of a tab is from the start of the paragraph which implies that when
+ the alignment of the paragraph is set to centered, the tab is interpreted to be
+ moved the same distance as the left ege of the paragraph does.
+ In case the paragraph is set to have a layoutDirection() RightToLeft the position
+ is interpreted to be from the right side of the paragraph with higher numbers moving
+ the tab to the left.
+*/
+
+/*!
+ \variable Tab::type
+ Determine which type is used.
+ In a paragraph that has layoutDirection() RightToLeft the type LeftTab will
+ be interpreted to be a RightTab and vice versa.
+*/
+
+/*!
+ \variable Tab::delimiter
+ If type is DelimitorTab; tab until this char is found in the text.
+*/
+
+/*!
+ \fn Tab::Tab()
+ Creates a default left tab with position 80.
+*/
+
+/*!
+ \fn bool Tab::operator==(const Tab &other) const
+
+ Returns true if tab \a other is equal to this tab;
+ otherwise returns false.
+*/
+
+/*!
+ \fn bool Tab::operator!=(const Tab &other) const
+
+ Returns true if tab \a other is not equal to this tab;
+ otherwise returns false.
+*/
+
+/*!
+ \fn void setTabs(QList<Tab> tabStops)
+ Set the Tab properties to \a tabStops.
+
+ \sa tabStop(), tabs()
+*/
+
+/*!
+ \since 4.4
+ \fn QList<QTextOption::Tab> QTextOption::tabs() const
+ Returns a list of tab positions defined for the text layout.
+
+ \sa tabStop(), setTabs(), setTabStop()
+*/
+
+
+QT_END_NAMESPACE
diff --git a/src/gui/text/qtextoption.h b/src/gui/text/qtextoption.h
new file mode 100644
index 0000000000..1c637a30d9
--- /dev/null
+++ b/src/gui/text/qtextoption.h
@@ -0,0 +1,161 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTOPTION_H
+#define QTEXTOPTION_H
+
+#include <QtCore/qnamespace.h>
+#include <QtCore/qchar.h>
+#include <QtCore/qmetatype.h>
+
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Gui)
+
+template <typename T> class QList;
+struct QTextOptionPrivate;
+
+class Q_GUI_EXPORT QTextOption
+{
+public:
+ enum TabType {
+ LeftTab,
+ RightTab,
+ CenterTab,
+ DelimiterTab
+ };
+
+ struct Q_GUI_EXPORT Tab {
+ inline Tab() : position(80), type(QTextOption::LeftTab) { }
+
+ inline bool operator==(const Tab &other) const {
+ return type == other.type
+ && qFuzzyCompare(position, other.position)
+ && delimiter == other.delimiter;
+ }
+
+ inline bool operator!=(const Tab &other) const {
+ return !operator==(other);
+ }
+
+ qreal position;
+ TabType type;
+ QChar delimiter;
+ };
+
+ QTextOption();
+ QTextOption(Qt::Alignment alignment);
+ ~QTextOption();
+
+ QTextOption(const QTextOption &o);
+ QTextOption &operator=(const QTextOption &o);
+
+ inline void setAlignment(Qt::Alignment alignment);
+ inline Qt::Alignment alignment() const { return Qt::Alignment(align); }
+
+ inline void setTextDirection(Qt::LayoutDirection aDirection) { this->direction = aDirection; }
+ inline Qt::LayoutDirection textDirection() const { return Qt::LayoutDirection(direction); }
+
+ enum WrapMode {
+ NoWrap,
+ WordWrap,
+ ManualWrap,
+ WrapAnywhere,
+ WrapAtWordBoundaryOrAnywhere
+ };
+ inline void setWrapMode(WrapMode wrap) { wordWrap = wrap; }
+ inline WrapMode wrapMode() const { return static_cast<WrapMode>(wordWrap); }
+
+ enum Flag {
+ ShowTabsAndSpaces = 0x1,
+ ShowLineAndParagraphSeparators = 0x2,
+ AddSpaceForLineAndParagraphSeparators = 0x4,
+ SuppressColors = 0x8,
+ IncludeTrailingSpaces = 0x80000000
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+ inline void setFlags(Flags flags);
+ inline Flags flags() const { return Flags(f); }
+
+ inline void setTabStop(qreal tabStop);
+ inline qreal tabStop() const { return tab; }
+
+ void setTabArray(QList<qreal> tabStops);
+ QList<qreal> tabArray() const;
+
+ void setTabs(QList<Tab> tabStops);
+ QList<Tab> tabs() const;
+
+ void setUseDesignMetrics(bool b) { design = b; }
+ bool useDesignMetrics() const { return design; }
+
+private:
+ uint align : 8;
+ uint wordWrap : 4;
+ uint design : 1;
+ uint direction : 1;
+ uint unused : 19;
+ uint f;
+ qreal tab;
+ QTextOptionPrivate *d;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QTextOption::Flags)
+
+inline void QTextOption::setAlignment(Qt::Alignment aalignment)
+{ align = aalignment; }
+
+inline void QTextOption::setFlags(Flags aflags)
+{ f = aflags; }
+
+inline void QTextOption::setTabStop(qreal atabStop)
+{ tab = atabStop; }
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE( QTextOption::Tab )
+
+QT_END_HEADER
+
+#endif // QTEXTOPTION_H
diff --git a/src/gui/text/qtexttable.cpp b/src/gui/text/qtexttable.cpp
new file mode 100644
index 0000000000..375bb0974f
--- /dev/null
+++ b/src/gui/text/qtexttable.cpp
@@ -0,0 +1,1290 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtexttable.h"
+#include "qtextcursor.h"
+#include "qtextformat.h"
+#include <qdebug.h>
+#include "qtexttable_p.h"
+#include "qvarlengtharray.h"
+#include "private/qfunctions_p.h"
+
+#include <stdlib.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QTextTableCell
+ \reentrant
+
+ \brief The QTextTableCell class represents the properties of a
+ cell in a QTextTable.
+
+ \ingroup text
+
+ Table cells are pieces of document structure that belong to a table.
+ The table orders cells into particular rows and columns; cells can
+ also span multiple columns and rows.
+
+ Cells are usually created when a table is inserted into a document with
+ QTextCursor::insertTable(), but they are also created and destroyed when
+ a table is resized.
+
+ Cells contain information about their location in a table; you can
+ obtain the row() and column() numbers of a cell, and its rowSpan()
+ and columnSpan().
+
+ The format() of a cell describes the default character format of its
+ contents. The firstCursorPosition() and lastCursorPosition() functions
+ are used to obtain the extent of the cell in the document.
+
+ \sa QTextTable QTextTableFormat
+*/
+
+/*!
+ \fn QTextTableCell::QTextTableCell()
+
+ Constructs an invalid table cell.
+
+ \sa isValid()
+*/
+
+/*!
+ \fn QTextTableCell::QTextTableCell(const QTextTableCell &other)
+
+ Copy constructor. Creates a new QTextTableCell object based on the
+ \a other cell.
+*/
+
+/*!
+ \fn QTextTableCell& QTextTableCell::operator=(const QTextTableCell &other)
+
+ Assigns the \a other table cell to this table cell.
+*/
+
+/*!
+ \since 4.2
+
+ Sets the cell's character format to \a format. This can for example be used to change
+ the background color of the entire cell:
+
+ QTextTableCell cell = table->cellAt(2, 3);
+ QTextCharFormat format = cell.format();
+ format.setBackground(Qt::blue);
+ cell.setFormat(format);
+
+ Note that the cell's row or column span cannot be changed through this function. You have
+ to use QTextTable::mergeCells and QTextTable::splitCell instead.
+
+ \sa format()
+*/
+void QTextTableCell::setFormat(const QTextCharFormat &format)
+{
+ QTextCharFormat fmt = format;
+ fmt.clearProperty(QTextFormat::ObjectIndex);
+ fmt.setObjectType(QTextFormat::TableCellObject);
+ QTextDocumentPrivate *p = table->docHandle();
+ QTextDocumentPrivate::FragmentIterator frag(&p->fragmentMap(), fragment);
+
+ QTextFormatCollection *c = p->formatCollection();
+ QTextCharFormat oldFormat = c->charFormat(frag->format);
+ fmt.setTableCellRowSpan(oldFormat.tableCellRowSpan());
+ fmt.setTableCellColumnSpan(oldFormat.tableCellColumnSpan());
+
+ p->setCharFormat(frag.position(), 1, fmt, QTextDocumentPrivate::SetFormatAndPreserveObjectIndices);
+}
+
+/*!
+ Returns the cell's character format.
+*/
+QTextCharFormat QTextTableCell::format() const
+{
+ QTextDocumentPrivate *p = table->docHandle();
+ QTextFormatCollection *c = p->formatCollection();
+
+ QTextCharFormat fmt = c->charFormat(tableCellFormatIndex());
+ fmt.setObjectType(QTextFormat::TableCellObject);
+ return fmt;
+}
+
+/*!
+ \since 4.5
+
+ Returns the index of the tableCell's format in the document's internal list of formats.
+
+ \sa QTextDocument::allFormats()
+*/
+int QTextTableCell::tableCellFormatIndex() const
+{
+ QTextDocumentPrivate *p = table->docHandle();
+ return QTextDocumentPrivate::FragmentIterator(&p->fragmentMap(), fragment)->format;
+}
+
+/*!
+ Returns the number of the row in the table that contains this cell.
+
+ \sa column()
+*/
+int QTextTableCell::row() const
+{
+ const QTextTablePrivate *tp = table->d_func();
+ if (tp->dirty)
+ tp->update();
+
+ int idx = tp->findCellIndex(fragment);
+ if (idx == -1)
+ return idx;
+ return tp->cellIndices.at(idx) / tp->nCols;
+}
+
+/*!
+ Returns the number of the column in the table that contains this cell.
+
+ \sa row()
+*/
+int QTextTableCell::column() const
+{
+ const QTextTablePrivate *tp = table->d_func();
+ if (tp->dirty)
+ tp->update();
+
+ int idx = tp->findCellIndex(fragment);
+ if (idx == -1)
+ return idx;
+ return tp->cellIndices.at(idx) % tp->nCols;
+}
+
+/*!
+ Returns the number of rows this cell spans. The default is 1.
+
+ \sa columnSpan()
+*/
+int QTextTableCell::rowSpan() const
+{
+ return format().tableCellRowSpan();
+}
+
+/*!
+ Returns the number of columns this cell spans. The default is 1.
+
+ \sa rowSpan()
+*/
+int QTextTableCell::columnSpan() const
+{
+ return format().tableCellColumnSpan();
+}
+
+/*!
+ \fn bool QTextTableCell::isValid() const
+
+ Returns true if this is a valid table cell; otherwise returns
+ false.
+*/
+
+
+/*!
+ Returns the first valid cursor position in this cell.
+
+ \sa lastCursorPosition()
+*/
+QTextCursor QTextTableCell::firstCursorPosition() const
+{
+ return QTextCursor(table->d_func()->pieceTable, firstPosition());
+}
+
+/*!
+ Returns the last valid cursor position in this cell.
+
+ \sa firstCursorPosition()
+*/
+QTextCursor QTextTableCell::lastCursorPosition() const
+{
+ return QTextCursor(table->d_func()->pieceTable, lastPosition());
+}
+
+
+/*!
+ \internal
+
+ Returns the first valid position in the document occupied by this cell.
+*/
+int QTextTableCell::firstPosition() const
+{
+ QTextDocumentPrivate *p = table->docHandle();
+ return p->fragmentMap().position(fragment) + 1;
+}
+
+/*!
+ \internal
+
+ Returns the last valid position in the document occupied by this cell.
+*/
+int QTextTableCell::lastPosition() const
+{
+ QTextDocumentPrivate *p = table->docHandle();
+ const QTextTablePrivate *td = table->d_func();
+ int index = table->d_func()->findCellIndex(fragment);
+ int f;
+ if (index != -1)
+ f = td->cells.value(index + 1, td->fragment_end);
+ else
+ f = td->fragment_end;
+ return p->fragmentMap().position(f);
+}
+
+
+/*!
+ Returns a frame iterator pointing to the beginning of the table's cell.
+
+ \sa end()
+*/
+QTextFrame::iterator QTextTableCell::begin() const
+{
+ QTextDocumentPrivate *p = table->docHandle();
+ int b = p->blockMap().findNode(firstPosition());
+ int e = p->blockMap().findNode(lastPosition()+1);
+ return QTextFrame::iterator(const_cast<QTextTable *>(table), b, b, e);
+}
+
+/*!
+ Returns a frame iterator pointing to the end of the table's cell.
+
+ \sa begin()
+*/
+QTextFrame::iterator QTextTableCell::end() const
+{
+ QTextDocumentPrivate *p = table->docHandle();
+ int b = p->blockMap().findNode(firstPosition());
+ int e = p->blockMap().findNode(lastPosition()+1);
+ return QTextFrame::iterator(const_cast<QTextTable *>(table), e, b, e);
+}
+
+
+/*!
+ \fn QTextCursor QTextTableCell::operator==(const QTextTableCell &other) const
+
+ Returns true if this cell object and the \a other cell object
+ describe the same cell; otherwise returns false.
+*/
+
+/*!
+ \fn QTextCursor QTextTableCell::operator!=(const QTextTableCell &other) const
+
+ Returns true if this cell object and the \a other cell object
+ describe different cells; otherwise returns false.
+*/
+
+/*!
+ \fn QTextTableCell::~QTextTableCell()
+
+ Destroys the table cell.
+*/
+
+QTextTablePrivate::~QTextTablePrivate()
+{
+ if (grid)
+ free(grid);
+}
+
+
+QTextTable *QTextTablePrivate::createTable(QTextDocumentPrivate *pieceTable, int pos, int rows, int cols, const QTextTableFormat &tableFormat)
+{
+ QTextTableFormat fmt = tableFormat;
+ fmt.setColumns(cols);
+ QTextTable *table = qobject_cast<QTextTable *>(pieceTable->createObject(fmt));
+ Q_ASSERT(table);
+
+ pieceTable->beginEditBlock();
+
+// qDebug("---> createTable: rows=%d, cols=%d at %d", rows, cols, pos);
+ // add block after table
+ QTextCharFormat charFmt;
+ charFmt.setObjectIndex(table->objectIndex());
+ charFmt.setObjectType(QTextFormat::TableCellObject);
+
+
+ int charIdx = pieceTable->formatCollection()->indexForFormat(charFmt);
+ int cellIdx = pieceTable->formatCollection()->indexForFormat(QTextBlockFormat());
+
+ QTextTablePrivate *d = table->d_func();
+ d->blockFragmentUpdates = true;
+
+ d->fragment_start = pieceTable->insertBlock(QTextBeginningOfFrame, pos, cellIdx, charIdx);
+ d->cells.append(d->fragment_start);
+ ++pos;
+
+ for (int i = 1; i < rows*cols; ++i) {
+ d->cells.append(pieceTable->insertBlock(QTextBeginningOfFrame, pos, cellIdx, charIdx));
+// qDebug(" addCell at %d", pos);
+ ++pos;
+ }
+
+ d->fragment_end = pieceTable->insertBlock(QTextEndOfFrame, pos, cellIdx, charIdx);
+// qDebug(" addEOR at %d", pos);
+ ++pos;
+
+ d->blockFragmentUpdates = false;
+ d->dirty = true;
+
+ pieceTable->endEditBlock();
+
+ return table;
+}
+
+struct QFragmentFindHelper
+{
+ inline QFragmentFindHelper(int _pos, const QTextDocumentPrivate::FragmentMap &map)
+ : pos(_pos), fragmentMap(map) {}
+ uint pos;
+ const QTextDocumentPrivate::FragmentMap &fragmentMap;
+};
+
+Q_STATIC_GLOBAL_INLINE_OPERATOR bool operator<(int fragment, const QFragmentFindHelper &helper)
+{
+ return helper.fragmentMap.position(fragment) < helper.pos;
+}
+
+Q_STATIC_GLOBAL_INLINE_OPERATOR bool operator<(const QFragmentFindHelper &helper, int fragment)
+{
+ return helper.pos < helper.fragmentMap.position(fragment);
+}
+
+int QTextTablePrivate::findCellIndex(int fragment) const
+{
+ QFragmentFindHelper helper(pieceTable->fragmentMap().position(fragment),
+ pieceTable->fragmentMap());
+ QList<int>::ConstIterator it = qBinaryFind(cells.begin(), cells.end(), helper);
+ if (it == cells.end())
+ return -1;
+ return it - cells.begin();
+}
+
+void QTextTablePrivate::fragmentAdded(const QChar &type, uint fragment)
+{
+ dirty = true;
+ if (blockFragmentUpdates)
+ return;
+ if (type == QTextBeginningOfFrame) {
+ Q_ASSERT(cells.indexOf(fragment) == -1);
+ const uint pos = pieceTable->fragmentMap().position(fragment);
+ QFragmentFindHelper helper(pos, pieceTable->fragmentMap());
+ QList<int>::Iterator it = qLowerBound(cells.begin(), cells.end(), helper);
+ cells.insert(it, fragment);
+ if (!fragment_start || pos < pieceTable->fragmentMap().position(fragment_start))
+ fragment_start = fragment;
+ return;
+ }
+ QTextFramePrivate::fragmentAdded(type, fragment);
+}
+
+void QTextTablePrivate::fragmentRemoved(const QChar &type, uint fragment)
+{
+ dirty = true;
+ if (blockFragmentUpdates)
+ return;
+ if (type == QTextBeginningOfFrame) {
+ Q_ASSERT(cells.indexOf(fragment) != -1);
+ cells.removeAll(fragment);
+ if (fragment_start == fragment && cells.size()) {
+ fragment_start = cells.at(0);
+ }
+ if (fragment_start != fragment)
+ return;
+ }
+ QTextFramePrivate::fragmentRemoved(type, fragment);
+}
+
+void QTextTablePrivate::update() const
+{
+ Q_Q(const QTextTable);
+ nCols = q->format().columns();
+ nRows = (cells.size() + nCols-1)/nCols;
+// qDebug(">>>> QTextTablePrivate::update, nRows=%d, nCols=%d", nRows, nCols);
+
+ grid = (int *)realloc(grid, nRows*nCols*sizeof(int));
+ memset(grid, 0, nRows*nCols*sizeof(int));
+
+ QTextDocumentPrivate *p = pieceTable;
+ QTextFormatCollection *c = p->formatCollection();
+
+ cellIndices.resize(cells.size());
+
+ int cell = 0;
+ for (int i = 0; i < cells.size(); ++i) {
+ int fragment = cells.at(i);
+ QTextCharFormat fmt = c->charFormat(QTextDocumentPrivate::FragmentIterator(&p->fragmentMap(), fragment)->format);
+ int rowspan = fmt.tableCellRowSpan();
+ int colspan = fmt.tableCellColumnSpan();
+
+ // skip taken cells
+ while (cell < nRows*nCols && grid[cell])
+ ++cell;
+
+ int r = cell/nCols;
+ int c = cell%nCols;
+ cellIndices[i] = cell;
+
+ if (r + rowspan > nRows) {
+ grid = (int *)realloc(grid, sizeof(int)*(r + rowspan)*nCols);
+ memset(grid + (nRows*nCols), 0, sizeof(int)*(r+rowspan-nRows)*nCols);
+ nRows = r + rowspan;
+ }
+
+ Q_ASSERT(c + colspan <= nCols);
+ for (int ii = 0; ii < rowspan; ++ii) {
+ for (int jj = 0; jj < colspan; ++jj) {
+ Q_ASSERT(grid[(r+ii)*nCols + c+jj] == 0);
+ grid[(r+ii)*nCols + c+jj] = fragment;
+// qDebug(" setting cell %d span=%d/%d at %d/%d", fragment, rowspan, colspan, r+ii, c+jj);
+ }
+ }
+ }
+// qDebug("<<<< end: nRows=%d, nCols=%d", nRows, nCols);
+
+ dirty = false;
+}
+
+
+
+
+
+/*!
+ \class QTextTable
+ \reentrant
+
+ \brief The QTextTable class represents a table in a QTextDocument.
+
+ \ingroup text
+
+ 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, and
+ is surrounded by a frame.
+
+ Tables are usually created and inserted into a document with the
+ QTextCursor::insertTable() function.
+ For example, we can insert a table with three rows and two columns at the
+ current cursor position in an editor using the following lines of code:
+
+ \snippet doc/src/snippets/textdocument-tables/mainwindow.cpp 1
+ \codeline
+ \snippet doc/src/snippets/textdocument-tables/mainwindow.cpp 3
+
+ The table format is either defined when the table is created or changed
+ later with setFormat().
+
+ The table currently being edited by the cursor is found with
+ QTextCursor::currentTable(). This allows its format or dimensions to be
+ changed after it has been inserted into a document.
+
+ A table's size can be changed with resize(), or by using
+ insertRows(), insertColumns(), removeRows(), or removeColumns().
+ Use cellAt() to retrieve table cells.
+
+ The starting and ending positions of table rows can be found by moving
+ a cursor within a table, and using the rowStart() and rowEnd() functions
+ to obtain cursors at the start and end of each row.
+
+ Rows and columns within a QTextTable can be merged and split using
+ the mergeCells() and splitCell() functions. However, only cells that span multiple
+ rows or columns can be split. (Merging or splitting does not increase or decrease
+ the number of rows and columns.)
+
+ \table 80%
+ \row
+ \o \inlineimage texttable-split.png Original Table
+ \o Suppose we have a 2x3 table of names and addresses. To merge both
+ columns in the first row we invoke mergeCells() with \a row = 0,
+ \a column = 0, \a numRows = 1 and \a numColumns = 2.
+ \snippet doc/src/snippets/textdocument-texttable/main.cpp 0
+
+ \row
+ \o \inlineimage texttable-merge.png
+ \o This gives us the following table. To split the first row of the table
+ back into two cells, we invoke the splitCell() function with \a numRows
+ and \a numCols = 1.
+ \snippet doc/src/snippets/textdocument-texttable/main.cpp 1
+
+ \row
+ \o \inlineimage texttable-split.png Split Table
+ \o This results in the original table.
+ \endtable
+
+ \sa QTextTableFormat
+*/
+
+/*! \internal
+ */
+QTextTable::QTextTable(QTextDocument *doc)
+ : QTextFrame(*new QTextTablePrivate, doc)
+{
+}
+
+/*! \internal
+
+Destroys the table.
+ */
+QTextTable::~QTextTable()
+{
+}
+
+
+/*!
+ \fn QTextTableCell QTextTable::cellAt(int row, int column) const
+
+ Returns the table cell at the given \a row and \a column in the table.
+
+ \sa columns() rows()
+*/
+QTextTableCell QTextTable::cellAt(int row, int col) const
+{
+ Q_D(const QTextTable);
+ if (d->dirty)
+ d->update();
+
+ if (row < 0 || row >= d->nRows || col < 0 || col >= d->nCols)
+ return QTextTableCell();
+
+ return QTextTableCell(this, d->grid[row*d->nCols + col]);
+}
+
+/*!
+ \overload
+
+ Returns the table cell that contains the character at the given \a position
+ in the document.
+*/
+QTextTableCell QTextTable::cellAt(int position) const
+{
+ Q_D(const QTextTable);
+ if (d->dirty)
+ d->update();
+
+ uint pos = (uint)position;
+ const QTextDocumentPrivate::FragmentMap &map = d->pieceTable->fragmentMap();
+ if (position < 0 || map.position(d->fragment_start) >= pos || map.position(d->fragment_end) < pos)
+ return QTextTableCell();
+
+ QFragmentFindHelper helper(position, map);
+ QList<int>::ConstIterator it = qLowerBound(d->cells.begin(), d->cells.end(), helper);
+ if (it != d->cells.begin())
+ --it;
+
+ return QTextTableCell(this, *it);
+}
+
+/*!
+ \fn QTextTableCell QTextTable::cellAt(const QTextCursor &cursor) const
+
+ \overload
+
+ Returns the table cell containing the given \a cursor.
+*/
+QTextTableCell QTextTable::cellAt(const QTextCursor &c) const
+{
+ return cellAt(c.position());
+}
+
+/*!
+ \fn void QTextTable::resize(int rows, int columns)
+
+ Resizes the table to contain the required number of \a rows and \a columns.
+
+ \sa insertRows() insertColumns() removeRows() removeColumns()
+*/
+void QTextTable::resize(int rows, int cols)
+{
+ Q_D(QTextTable);
+ if (d->dirty)
+ d->update();
+
+ int nRows = this->rows();
+ int nCols = this->columns();
+
+ if (rows == nRows && cols == nCols)
+ return;
+
+ d->pieceTable->beginEditBlock();
+
+ if (nCols < cols)
+ insertColumns(nCols, cols - nCols);
+ else if (nCols > cols)
+ removeColumns(cols, nCols - cols);
+
+ if (nRows < rows)
+ insertRows(nRows, rows-nRows);
+ else if (nRows > rows)
+ removeRows(rows, nRows-rows);
+
+ d->pieceTable->endEditBlock();
+}
+
+/*!
+ \fn void QTextTable::insertRows(int index, int rows)
+
+ Inserts a number of \a rows before the row with the specified \a index.
+
+ \sa resize() insertColumns() removeRows() removeColumns() appendRows() appendColumns()
+*/
+void QTextTable::insertRows(int pos, int num)
+{
+ Q_D(QTextTable);
+ if (num <= 0)
+ return;
+
+ if (d->dirty)
+ d->update();
+
+ if (pos > d->nRows || pos < 0)
+ pos = d->nRows;
+
+// qDebug() << "-------- insertRows" << pos << num;
+ QTextDocumentPrivate *p = d->pieceTable;
+ QTextFormatCollection *c = p->formatCollection();
+ p->beginEditBlock();
+
+ int extended = 0;
+ int insert_before = 0;
+ if (pos > 0 && pos < d->nRows) {
+ for (int i = 0; i < d->nCols; ++i) {
+ int cell = d->grid[pos*d->nCols + i];
+ if (cell == d->grid[(pos-1)*d->nCols+i]) {
+ // cell spans the insertion place, extend it
+ QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
+ QTextCharFormat fmt = c->charFormat(it->format);
+ fmt.setTableCellRowSpan(fmt.tableCellRowSpan() + num);
+ p->setCharFormat(it.position(), 1, fmt);
+ extended++;
+ } else if (!insert_before) {
+ insert_before = cell;
+ }
+ }
+ } else {
+ insert_before = (pos == 0 ? d->grid[0] : d->fragment_end);
+ }
+ if (extended < d->nCols) {
+ Q_ASSERT(insert_before);
+ QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), insert_before);
+ QTextCharFormat fmt = c->charFormat(it->format);
+ fmt.setTableCellRowSpan(1);
+ fmt.setTableCellColumnSpan(1);
+ Q_ASSERT(fmt.objectIndex() == objectIndex());
+ int pos = it.position();
+ int cfmt = p->formatCollection()->indexForFormat(fmt);
+ int bfmt = p->formatCollection()->indexForFormat(QTextBlockFormat());
+// qDebug("inserting %d cells, nCols=%d extended=%d", num*(d->nCols-extended), d->nCols, extended);
+ for (int i = 0; i < num*(d->nCols-extended); ++i)
+ p->insertBlock(QTextBeginningOfFrame, pos, bfmt, cfmt, QTextUndoCommand::MoveCursor);
+ }
+
+// qDebug() << "-------- end insertRows" << pos << num;
+ p->endEditBlock();
+}
+
+/*!
+ \fn void QTextTable::insertColumns(int index, int columns)
+
+ Inserts a number of \a columns before the column with the specified \a index.
+
+ \sa insertRows() resize() removeRows() removeColumns() appendRows() appendColumns()
+*/
+void QTextTable::insertColumns(int pos, int num)
+{
+ Q_D(QTextTable);
+ if (num <= 0)
+ return;
+
+ if (d->dirty)
+ d->update();
+
+ if (pos > d->nCols || pos < 0)
+ pos = d->nCols;
+
+// qDebug() << "-------- insertCols" << pos << num;
+ QTextDocumentPrivate *p = d->pieceTable;
+ QTextFormatCollection *c = p->formatCollection();
+ p->beginEditBlock();
+
+ for (int i = 0; i < d->nRows; ++i) {
+ int cell;
+ if (i == d->nRows - 1 && pos == d->nCols)
+ cell = d->fragment_end;
+ else
+ cell = d->grid[i*d->nCols + pos];
+ QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
+ QTextCharFormat fmt = c->charFormat(it->format);
+ if (pos > 0 && pos < d->nCols && cell == d->grid[i*d->nCols + pos - 1]) {
+ // cell spans the insertion place, extend it
+ fmt.setTableCellColumnSpan(fmt.tableCellColumnSpan() + num);
+ p->setCharFormat(it.position(), 1, fmt);
+ } else {
+ fmt.setTableCellRowSpan(1);
+ fmt.setTableCellColumnSpan(1);
+ Q_ASSERT(fmt.objectIndex() == objectIndex());
+ int position = it.position();
+ int cfmt = p->formatCollection()->indexForFormat(fmt);
+ int bfmt = p->formatCollection()->indexForFormat(QTextBlockFormat());
+ for (int i = 0; i < num; ++i)
+ p->insertBlock(QTextBeginningOfFrame, position, bfmt, cfmt, QTextUndoCommand::MoveCursor);
+ }
+ }
+
+ QTextTableFormat tfmt = format();
+ tfmt.setColumns(tfmt.columns()+num);
+ QVector<QTextLength> columnWidths = tfmt.columnWidthConstraints();
+ if (! columnWidths.isEmpty()) {
+ for (int i = num; i > 0; --i)
+ columnWidths.insert(pos, columnWidths[qMax(0, pos-1)]);
+ }
+ tfmt.setColumnWidthConstraints (columnWidths);
+ QTextObject::setFormat(tfmt);
+
+// qDebug() << "-------- end insertCols" << pos << num;
+ p->endEditBlock();
+}
+
+/*!
+ \since 4.5
+ Appends \a count rows at the bottom of the table.
+
+ \sa insertColumns() insertRows() resize() removeRows() removeColumns() appendColumns()
+*/
+void QTextTable::appendRows(int count)
+{
+ insertRows(rows(), count);
+}
+
+/*!
+ \since 4.5
+ Appends \a count columns at the right side of the table.
+
+ \sa insertColumns() insertRows() resize() removeRows() removeColumns() appendRows()
+*/
+void QTextTable::appendColumns(int count)
+{
+ insertColumns(columns(), count);
+}
+
+/*!
+ \fn void QTextTable::removeRows(int index, int rows)
+
+ Removes a number of \a rows starting with the row at the specified \a index.
+
+ \sa insertRows(), insertColumns(), resize(), removeColumns() appendRows() appendColumns()
+*/
+void QTextTable::removeRows(int pos, int num)
+{
+ Q_D(QTextTable);
+// qDebug() << "-------- removeRows" << pos << num;
+
+ if (num <= 0 || pos < 0)
+ return;
+ if (d->dirty)
+ d->update();
+ if (pos >= d->nRows)
+ return;
+ if (pos+num > d->nRows)
+ num = d->nRows - pos;
+
+ QTextDocumentPrivate *p = d->pieceTable;
+ QTextFormatCollection *collection = p->formatCollection();
+ p->beginEditBlock();
+
+ // delete whole table?
+ if (pos == 0 && num == d->nRows) {
+ const int pos = p->fragmentMap().position(d->fragment_start);
+ p->remove(pos, p->fragmentMap().position(d->fragment_end) - pos + 1);
+ p->endEditBlock();
+ return;
+ }
+
+ p->aboutToRemoveCell(cellAt(pos, 0).firstPosition(), cellAt(pos + num - 1, d->nCols - 1).lastPosition());
+
+ QList<int> touchedCells;
+ for (int r = pos; r < pos + num; ++r) {
+ for (int c = 0; c < d->nCols; ++c) {
+ int cell = d->grid[r*d->nCols + c];
+ if (touchedCells.contains(cell))
+ continue;
+ touchedCells << cell;
+ QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
+ QTextCharFormat fmt = collection->charFormat(it->format);
+ int span = fmt.tableCellRowSpan();
+ if (span > 1) {
+ fmt.setTableCellRowSpan(span - 1);
+ p->setCharFormat(it.position(), 1, fmt);
+ } else {
+ // remove cell
+ int index = d->cells.indexOf(cell) + 1;
+ int f_end = index < d->cells.size() ? d->cells.at(index) : d->fragment_end;
+ p->remove(it.position(), p->fragmentMap().position(f_end) - it.position());
+ }
+ }
+ }
+
+ p->endEditBlock();
+// qDebug() << "-------- end removeRows" << pos << num;
+}
+
+/*!
+ \fn void QTextTable::removeColumns(int index, int columns)
+
+ Removes a number of \a columns starting with the column at the specified
+ \a index.
+
+ \sa insertRows() insertColumns() removeRows() resize() appendRows() appendColumns()
+*/
+void QTextTable::removeColumns(int pos, int num)
+{
+ Q_D(QTextTable);
+// qDebug() << "-------- removeCols" << pos << num;
+
+ if (num <= 0 || pos < 0)
+ return;
+ if (d->dirty)
+ d->update();
+ if (pos >= d->nCols)
+ return;
+ if (pos + num > d->nCols)
+ pos = d->nCols - num;
+
+ QTextDocumentPrivate *p = d->pieceTable;
+ QTextFormatCollection *collection = p->formatCollection();
+ p->beginEditBlock();
+
+ // delete whole table?
+ if (pos == 0 && num == d->nCols) {
+ const int pos = p->fragmentMap().position(d->fragment_start);
+ p->remove(pos, p->fragmentMap().position(d->fragment_end) - pos + 1);
+ p->endEditBlock();
+ return;
+ }
+
+ p->aboutToRemoveCell(cellAt(0, pos).firstPosition(), cellAt(d->nRows - 1, pos + num - 1).lastPosition());
+
+ QList<int> touchedCells;
+ for (int r = 0; r < d->nRows; ++r) {
+ for (int c = pos; c < pos + num; ++c) {
+ int cell = d->grid[r*d->nCols + c];
+ if (touchedCells.contains(cell))
+ continue;
+ touchedCells << cell;
+ QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), cell);
+ QTextCharFormat fmt = collection->charFormat(it->format);
+ int span = fmt.tableCellColumnSpan();
+ if (span > 1) {
+ fmt.setTableCellColumnSpan(span - 1);
+ p->setCharFormat(it.position(), 1, fmt);
+ } else {
+ // remove cell
+ int index = d->cells.indexOf(cell) + 1;
+ int f_end = index < d->cells.size() ? d->cells.at(index) : d->fragment_end;
+ p->remove(it.position(), p->fragmentMap().position(f_end) - it.position());
+ }
+ }
+ }
+
+ QTextTableFormat tfmt = format();
+ tfmt.setColumns(tfmt.columns()-num);
+ QVector<QTextLength> columnWidths = tfmt.columnWidthConstraints();
+ if (columnWidths.count() > pos) {
+ columnWidths.remove(pos, num);
+ tfmt.setColumnWidthConstraints (columnWidths);
+ }
+ QTextObject::setFormat(tfmt);
+
+ p->endEditBlock();
+// qDebug() << "-------- end removeCols" << pos << num;
+}
+
+/*!
+ \since 4.1
+
+ Merges the cell at the specified \a row and \a column with the adjacent cells
+ into one cell. The new cell will span \a numRows rows and \a numCols columns.
+ If \a numRows or \a numCols is less than the current number of rows or columns
+ the cell spans then this method does nothing.
+
+ \sa splitCell()
+*/
+void QTextTable::mergeCells(int row, int column, int numRows, int numCols)
+{
+ Q_D(QTextTable);
+
+ if (d->dirty)
+ d->update();
+
+ QTextDocumentPrivate *p = d->pieceTable;
+ QTextFormatCollection *fc = p->formatCollection();
+
+ const QTextTableCell cell = cellAt(row, column);
+ if (!cell.isValid() || row != cell.row() || column != cell.column())
+ return;
+
+ QTextCharFormat fmt = cell.format();
+ const int rowSpan = fmt.tableCellRowSpan();
+ const int colSpan = fmt.tableCellColumnSpan();
+
+ numRows = qMin(numRows, rows() - cell.row());
+ numCols = qMin(numCols, columns() - cell.column());
+
+ // nothing to merge?
+ if (numRows < rowSpan || numCols < colSpan)
+ return;
+
+ // check the edges of the merge rect to make sure no cell spans the edge
+ for (int r = row; r < row + numRows; ++r) {
+ if (cellAt(r, column) == cellAt(r, column - 1))
+ return;
+ if (cellAt(r, column + numCols) == cellAt(r, column + numCols - 1))
+ return;
+ }
+
+ for (int c = column; c < column + numCols; ++c) {
+ if (cellAt(row, c) == cellAt(row - 1, c))
+ return;
+ if (cellAt(row + numRows, c) == cellAt(row + numRows - 1, c))
+ return;
+ }
+
+ p->beginEditBlock();
+
+ const int origCellPosition = cell.firstPosition() - 1;
+
+ const int cellFragment = d->grid[row * d->nCols + column];
+
+ // find the position at which to insert the contents of the merged cells
+ QFragmentFindHelper helper(origCellPosition, p->fragmentMap());
+ QList<int>::Iterator it = qBinaryFind(d->cells.begin(), d->cells.end(), helper);
+ Q_ASSERT(it != d->cells.end());
+ Q_ASSERT(*it == cellFragment);
+ const int insertCellIndex = it - d->cells.begin();
+ int insertFragment = d->cells.value(insertCellIndex + 1, d->fragment_end);
+ uint insertPos = p->fragmentMap().position(insertFragment);
+
+ d->blockFragmentUpdates = true;
+
+ bool rowHasText = cell.firstCursorPosition().block().length();
+ bool needsParagraph = rowHasText && colSpan == numCols;
+
+ // find all cells that will be erased by the merge
+ for (int r = row; r < row + numRows; ++r) {
+ int firstColumn = r < row + rowSpan ? column + colSpan : column;
+
+ // don't recompute the cell index for the first row
+ int firstCellIndex = r == row ? insertCellIndex + 1 : -1;
+ int cellIndex = firstCellIndex;
+
+ for (int c = firstColumn; c < column + numCols; ++c) {
+ const int fragment = d->grid[r * d->nCols + c];
+
+ // already handled?
+ if (fragment == cellFragment)
+ continue;
+
+ QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), fragment);
+ uint pos = it.position();
+
+ if (firstCellIndex == -1) {
+ QFragmentFindHelper helper(pos, p->fragmentMap());
+ QList<int>::Iterator it = qBinaryFind(d->cells.begin(), d->cells.end(), helper);
+ Q_ASSERT(it != d->cells.end());
+ Q_ASSERT(*it == fragment);
+ firstCellIndex = cellIndex = it - d->cells.begin();
+ }
+
+ ++cellIndex;
+
+ QTextCharFormat fmt = fc->charFormat(it->format);
+
+ const int cellRowSpan = fmt.tableCellRowSpan();
+ const int cellColSpan = fmt.tableCellColumnSpan();
+
+ // update the grid for this cell
+ for (int i = r; i < r + cellRowSpan; ++i)
+ for (int j = c; j < c + cellColSpan; ++j)
+ d->grid[i * d->nCols + j] = cellFragment;
+
+ // erase the cell marker
+ p->remove(pos, 1);
+
+ const int nextFragment = d->cells.value(cellIndex, d->fragment_end);
+ const uint nextPos = p->fragmentMap().position(nextFragment);
+
+ Q_ASSERT(nextPos >= pos);
+
+ // merge the contents of the cell (if not empty)
+ if (nextPos > pos) {
+ if (needsParagraph) {
+ needsParagraph = false;
+ QTextCursor(p, insertPos++).insertBlock();
+ p->move(pos + 1, insertPos, nextPos - pos);
+ } else if (rowHasText) {
+ QTextCursor(p, insertPos++).insertText(QLatin1String(" "));
+ p->move(pos + 1, insertPos, nextPos - pos);
+ } else {
+ p->move(pos, insertPos, nextPos - pos);
+ }
+
+ insertPos += nextPos - pos;
+ rowHasText = true;
+ }
+ }
+
+ if (rowHasText) {
+ needsParagraph = true;
+ rowHasText = false;
+ }
+
+ // erase cells from last row
+ if (firstCellIndex >= 0) {
+ d->cellIndices.remove(firstCellIndex, cellIndex - firstCellIndex);
+ d->cells.erase(d->cells.begin() + firstCellIndex, d->cells.begin() + cellIndex);
+ }
+ }
+
+ d->fragment_start = d->cells.first();
+
+ fmt.setTableCellRowSpan(numRows);
+ fmt.setTableCellColumnSpan(numCols);
+ p->setCharFormat(origCellPosition, 1, fmt);
+
+ d->blockFragmentUpdates = false;
+ d->dirty = false;
+
+ p->endEditBlock();
+}
+
+/*!
+ \overload
+ \since 4.1
+
+ Merges the cells selected by the provided \a cursor.
+
+ \sa splitCell()
+*/
+void QTextTable::mergeCells(const QTextCursor &cursor)
+{
+ if (!cursor.hasComplexSelection())
+ return;
+
+ int firstRow, numRows, firstColumn, numColumns;
+ cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns);
+ mergeCells(firstRow, firstColumn, numRows, numColumns);
+}
+
+/*!
+ \since 4.1
+
+ Splits the specified cell at \a row and \a column into an array of multiple
+ cells with dimensions specified by \a numRows and \a numCols.
+
+ \note It is only possible to split cells that span multiple rows or columns, such as rows
+ that have been merged using mergeCells().
+
+ \sa mergeCells()
+*/
+void QTextTable::splitCell(int row, int column, int numRows, int numCols)
+{
+ Q_D(QTextTable);
+
+ if (d->dirty)
+ d->update();
+
+ QTextDocumentPrivate *p = d->pieceTable;
+ QTextFormatCollection *c = p->formatCollection();
+
+ const QTextTableCell cell = cellAt(row, column);
+ if (!cell.isValid())
+ return;
+ row = cell.row();
+ column = cell.column();
+
+ QTextCharFormat fmt = cell.format();
+ const int rowSpan = fmt.tableCellRowSpan();
+ const int colSpan = fmt.tableCellColumnSpan();
+
+ // nothing to split?
+ if (numRows > rowSpan || numCols > colSpan)
+ return;
+
+ p->beginEditBlock();
+
+ const int origCellPosition = cell.firstPosition() - 1;
+
+ QVarLengthArray<int> rowPositions(rowSpan);
+
+ rowPositions[0] = cell.lastPosition();
+
+ for (int r = row + 1; r < row + rowSpan; ++r) {
+ // find the cell before which to insert the new cell markers
+ int gridIndex = r * d->nCols + column;
+ QVector<int>::iterator it = qUpperBound(d->cellIndices.begin(), d->cellIndices.end(), gridIndex);
+ int cellIndex = it - d->cellIndices.begin();
+ int fragment = d->cells.value(cellIndex, d->fragment_end);
+ rowPositions[r - row] = p->fragmentMap().position(fragment);
+ }
+
+ fmt.setTableCellColumnSpan(1);
+ fmt.setTableCellRowSpan(1);
+ const int fmtIndex = c->indexForFormat(fmt);
+ const int blockIndex = p->blockMap().find(cell.lastPosition())->format;
+
+ int insertAdjustement = 0;
+ for (int i = 0; i < numRows; ++i) {
+ for (int c = 0; c < colSpan - numCols; ++c)
+ p->insertBlock(QTextBeginningOfFrame, rowPositions[i] + insertAdjustement + c, blockIndex, fmtIndex);
+ insertAdjustement += colSpan - numCols;
+ }
+
+ for (int i = numRows; i < rowSpan; ++i) {
+ for (int c = 0; c < colSpan; ++c)
+ p->insertBlock(QTextBeginningOfFrame, rowPositions[i] + insertAdjustement + c, blockIndex, fmtIndex);
+ insertAdjustement += colSpan;
+ }
+
+ fmt.setTableCellRowSpan(numRows);
+ fmt.setTableCellColumnSpan(numCols);
+ p->setCharFormat(origCellPosition, 1, fmt);
+
+ p->endEditBlock();
+}
+
+/*!
+ Returns the number of rows in the table.
+
+ \sa columns()
+*/
+int QTextTable::rows() const
+{
+ Q_D(const QTextTable);
+ if (d->dirty)
+ d->update();
+
+ return d->nRows;
+}
+
+/*!
+ Returns the number of columns in the table.
+
+ \sa rows()
+*/
+int QTextTable::columns() const
+{
+ Q_D(const QTextTable);
+ if (d->dirty)
+ d->update();
+
+ return d->nCols;
+}
+
+#if 0
+void QTextTable::mergeCells(const QTextCursor &selection)
+{
+}
+#endif
+
+/*!
+ \fn QTextCursor QTextTable::rowStart(const QTextCursor &cursor) const
+
+ Returns a cursor pointing to the start of the row that contains the
+ given \a cursor.
+
+ \sa rowEnd()
+*/
+QTextCursor QTextTable::rowStart(const QTextCursor &c) const
+{
+ Q_D(const QTextTable);
+ QTextTableCell cell = cellAt(c);
+ if (!cell.isValid())
+ return QTextCursor();
+
+ int row = cell.row();
+ QTextDocumentPrivate *p = d->pieceTable;
+ QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), d->grid[row*d->nCols]);
+ return QTextCursor(p, it.position());
+}
+
+/*!
+ \fn QTextCursor QTextTable::rowEnd(const QTextCursor &cursor) const
+
+ Returns a cursor pointing to the end of the row that contains the given
+ \a cursor.
+
+ \sa rowStart()
+*/
+QTextCursor QTextTable::rowEnd(const QTextCursor &c) const
+{
+ Q_D(const QTextTable);
+ QTextTableCell cell = cellAt(c);
+ if (!cell.isValid())
+ return QTextCursor();
+
+ int row = cell.row() + 1;
+ int fragment = row < d->nRows ? d->grid[row*d->nCols] : d->fragment_end;
+ QTextDocumentPrivate *p = d->pieceTable;
+ QTextDocumentPrivate::FragmentIterator it(&p->fragmentMap(), fragment);
+ return QTextCursor(p, it.position() - 1);
+}
+
+/*!
+ \fn void QTextTable::setFormat(const QTextTableFormat &format)
+
+ Sets the table's \a format.
+
+ \sa format()
+*/
+void QTextTable::setFormat(const QTextTableFormat &format)
+{
+ QTextTableFormat fmt = format;
+ // don't try to change the number of table columns from here
+ fmt.setColumns(columns());
+ QTextObject::setFormat(fmt);
+}
+
+/*!
+ \fn QTextTableFormat QTextTable::format() const
+
+ Returns the table's format.
+
+ \sa setFormat()
+*/
+
+QT_END_NAMESPACE
diff --git a/src/gui/text/qtexttable.h b/src/gui/text/qtexttable.h
new file mode 100644
index 0000000000..76cd4275f3
--- /dev/null
+++ b/src/gui/text/qtexttable.h
@@ -0,0 +1,145 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTTABLE_H
+#define QTEXTTABLE_H
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qobject.h>
+#include <QtGui/qtextobject.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Gui)
+
+class QTextCursor;
+class QTextTable;
+class QTextTablePrivate;
+
+class Q_GUI_EXPORT QTextTableCell
+{
+public:
+ QTextTableCell() : table(0) {}
+ ~QTextTableCell() {}
+ QTextTableCell(const QTextTableCell &o) : table(o.table), fragment(o.fragment) {}
+ QTextTableCell &operator=(const QTextTableCell &o)
+ { table = o.table; fragment = o.fragment; return *this; }
+
+ void setFormat(const QTextCharFormat &format);
+ QTextCharFormat format() const;
+
+ int row() const;
+ int column() const;
+
+ int rowSpan() const;
+ int columnSpan() const;
+
+ inline bool isValid() const { return table != 0; }
+
+ QTextCursor firstCursorPosition() const;
+ QTextCursor lastCursorPosition() const;
+ int firstPosition() const;
+ int lastPosition() const;
+
+ inline bool operator==(const QTextTableCell &other) const
+ { return table == other.table && fragment == other.fragment; }
+ inline bool operator!=(const QTextTableCell &other) const
+ { return !operator==(other); }
+
+ QTextFrame::iterator begin() const;
+ QTextFrame::iterator end() const;
+
+ int tableCellFormatIndex() const;
+
+private:
+ friend class QTextTable;
+ QTextTableCell(const QTextTable *t, int f)
+ : table(t), fragment(f) {}
+
+ const QTextTable *table;
+ int fragment;
+};
+
+class Q_GUI_EXPORT QTextTable : public QTextFrame
+{
+ Q_OBJECT
+public:
+ explicit QTextTable(QTextDocument *doc);
+ ~QTextTable();
+
+ void resize(int rows, int cols);
+ void insertRows(int pos, int num);
+ void insertColumns(int pos, int num);
+ void appendRows(int count);
+ void appendColumns(int count);
+ void removeRows(int pos, int num);
+ void removeColumns(int pos, int num);
+
+ void mergeCells(int row, int col, int numRows, int numCols);
+ void mergeCells(const QTextCursor &cursor);
+ void splitCell(int row, int col, int numRows, int numCols);
+
+ int rows() const;
+ int columns() const;
+
+ QTextTableCell cellAt(int row, int col) const;
+ QTextTableCell cellAt(int position) const;
+ QTextTableCell cellAt(const QTextCursor &c) const;
+
+ QTextCursor rowStart(const QTextCursor &c) const;
+ QTextCursor rowEnd(const QTextCursor &c) const;
+
+ void setFormat(const QTextTableFormat &format);
+ QTextTableFormat format() const { return QTextObject::format().toTableFormat(); }
+
+private:
+ Q_DISABLE_COPY(QTextTable)
+ Q_DECLARE_PRIVATE(QTextTable)
+ friend class QTextTableCell;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QTEXTTABLE_H
diff --git a/src/gui/text/qtexttable_p.h b/src/gui/text/qtexttable_p.h
new file mode 100644
index 0000000000..1ba3a3f129
--- /dev/null
+++ b/src/gui/text/qtexttable_p.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTEXTTABLE_P_H
+#define QTEXTTABLE_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/qtextobject_p.h"
+#include "private/qtextdocument_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QTextTablePrivate : public QTextFramePrivate
+{
+ Q_DECLARE_PUBLIC(QTextTable)
+public:
+ QTextTablePrivate() : grid(0), nRows(0), dirty(true), blockFragmentUpdates(false) {}
+ ~QTextTablePrivate();
+
+ static QTextTable *createTable(QTextDocumentPrivate *, int pos, int rows, int cols, const QTextTableFormat &tableFormat);
+ void fragmentAdded(const QChar &type, uint fragment);
+ void fragmentRemoved(const QChar &type, uint fragment);
+
+ void update() const;
+
+ int findCellIndex(int fragment) const;
+
+ QList<int> cells;
+ // symmetric to cells array and maps to indecs in grid,
+ // used for fast-lookup for row/column by fragment
+ mutable QVector<int> cellIndices;
+ mutable int *grid;
+ mutable int nRows;
+ mutable int nCols;
+ mutable bool dirty;
+ bool blockFragmentUpdates;
+};
+
+QT_END_NAMESPACE
+
+#endif // QTEXTTABLE_P_H
diff --git a/src/gui/text/qzip.cpp b/src/gui/text/qzip.cpp
new file mode 100644
index 0000000000..c6c2e6913f
--- /dev/null
+++ b/src/gui/text/qzip.cpp
@@ -0,0 +1,1208 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qglobal.h>
+
+#ifndef QT_NO_TEXTODFWRITER
+
+#include "qzipreader_p.h"
+#include "qzipwriter_p.h"
+#include <qdatetime.h>
+#include <qplatformdefs.h>
+#include <qendian.h>
+#include <qdebug.h>
+#include <qdir.h>
+
+#include <zlib.h>
+
+#if defined(Q_OS_WIN)
+#undef S_IFREG
+#define S_IFREG 0100000
+# define S_ISDIR(x) ((x) & 0040000) > 0
+# define S_ISREG(x) ((x) & 0170000) == S_IFREG
+# define S_IFLNK 020000
+# define S_ISLNK(x) ((x) & S_IFLNK) > 0
+# define S_IRUSR 0400
+# define S_IWUSR 0200
+# define S_IXUSR 0100
+# define S_IRGRP 0040
+# define S_IWGRP 0020
+# define S_IXGRP 0010
+# define S_IROTH 0004
+# define S_IWOTH 0002
+# define S_IXOTH 0001
+#endif
+
+#if 0
+#define ZDEBUG qDebug
+#else
+#define ZDEBUG if (0) qDebug
+#endif
+
+QT_BEGIN_NAMESPACE
+
+static inline uint readUInt(const uchar *data)
+{
+ return (data[0]) + (data[1]<<8) + (data[2]<<16) + (data[3]<<24);
+}
+
+static inline ushort readUShort(const uchar *data)
+{
+ return (data[0]) + (data[1]<<8);
+}
+
+static inline void writeUInt(uchar *data, uint i)
+{
+ data[0] = i & 0xff;
+ data[1] = (i>>8) & 0xff;
+ data[2] = (i>>16) & 0xff;
+ data[3] = (i>>24) & 0xff;
+}
+
+static inline void writeUShort(uchar *data, ushort i)
+{
+ data[0] = i & 0xff;
+ data[1] = (i>>8) & 0xff;
+}
+
+static inline void copyUInt(uchar *dest, const uchar *src)
+{
+ dest[0] = src[0];
+ dest[1] = src[1];
+ dest[2] = src[2];
+ dest[3] = src[3];
+}
+
+static inline void copyUShort(uchar *dest, const uchar *src)
+{
+ dest[0] = src[0];
+ dest[1] = src[1];
+}
+
+static void writeMSDosDate(uchar *dest, const QDateTime& dt)
+{
+ if (dt.isValid()) {
+ quint16 time =
+ (dt.time().hour() << 11) // 5 bit hour
+ | (dt.time().minute() << 5) // 6 bit minute
+ | (dt.time().second() >> 1); // 5 bit double seconds
+
+ dest[0] = time & 0xff;
+ dest[1] = time >> 8;
+
+ quint16 date =
+ ((dt.date().year() - 1980) << 9) // 7 bit year 1980-based
+ | (dt.date().month() << 5) // 4 bit month
+ | (dt.date().day()); // 5 bit day
+
+ dest[2] = char(date);
+ dest[3] = char(date >> 8);
+ } else {
+ dest[0] = 0;
+ dest[1] = 0;
+ dest[2] = 0;
+ dest[3] = 0;
+ }
+}
+
+static quint32 permissionsToMode(QFile::Permissions perms)
+{
+ quint32 mode = 0;
+ if (perms & QFile::ReadOwner)
+ mode |= S_IRUSR;
+ if (perms & QFile::WriteOwner)
+ mode |= S_IWUSR;
+ if (perms & QFile::ExeOwner)
+ mode |= S_IXUSR;
+ if (perms & QFile::ReadUser)
+ mode |= S_IRUSR;
+ if (perms & QFile::WriteUser)
+ mode |= S_IWUSR;
+ if (perms & QFile::ExeUser)
+ mode |= S_IXUSR;
+ if (perms & QFile::ReadGroup)
+ mode |= S_IRGRP;
+ if (perms & QFile::WriteGroup)
+ mode |= S_IWGRP;
+ if (perms & QFile::ExeGroup)
+ mode |= S_IXGRP;
+ if (perms & QFile::ReadOther)
+ mode |= S_IROTH;
+ if (perms & QFile::WriteOther)
+ mode |= S_IWOTH;
+ if (perms & QFile::ExeOther)
+ mode |= S_IXOTH;
+ return mode;
+}
+
+static int inflate(Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen)
+{
+ z_stream stream;
+ int err;
+
+ stream.next_in = (Bytef*)source;
+ stream.avail_in = (uInt)sourceLen;
+ if ((uLong)stream.avail_in != sourceLen)
+ return Z_BUF_ERROR;
+
+ stream.next_out = dest;
+ stream.avail_out = (uInt)*destLen;
+ if ((uLong)stream.avail_out != *destLen)
+ return Z_BUF_ERROR;
+
+ stream.zalloc = (alloc_func)0;
+ stream.zfree = (free_func)0;
+
+ err = inflateInit2(&stream, -MAX_WBITS);
+ if (err != Z_OK)
+ return err;
+
+ err = inflate(&stream, Z_FINISH);
+ if (err != Z_STREAM_END) {
+ inflateEnd(&stream);
+ if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
+ return Z_DATA_ERROR;
+ return err;
+ }
+ *destLen = stream.total_out;
+
+ err = inflateEnd(&stream);
+ return err;
+}
+
+static int deflate (Bytef *dest, ulong *destLen, const Bytef *source, ulong sourceLen)
+{
+ z_stream stream;
+ int err;
+
+ stream.next_in = (Bytef*)source;
+ stream.avail_in = (uInt)sourceLen;
+ stream.next_out = dest;
+ stream.avail_out = (uInt)*destLen;
+ if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
+
+ stream.zalloc = (alloc_func)0;
+ stream.zfree = (free_func)0;
+ stream.opaque = (voidpf)0;
+
+ err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);
+ if (err != Z_OK) return err;
+
+ err = deflate(&stream, Z_FINISH);
+ if (err != Z_STREAM_END) {
+ deflateEnd(&stream);
+ return err == Z_OK ? Z_BUF_ERROR : err;
+ }
+ *destLen = stream.total_out;
+
+ err = deflateEnd(&stream);
+ return err;
+}
+
+static QFile::Permissions modeToPermissions(quint32 mode)
+{
+ QFile::Permissions ret;
+ if (mode & S_IRUSR)
+ ret |= QFile::ReadOwner;
+ if (mode & S_IWUSR)
+ ret |= QFile::WriteOwner;
+ if (mode & S_IXUSR)
+ ret |= QFile::ExeOwner;
+ if (mode & S_IRUSR)
+ ret |= QFile::ReadUser;
+ if (mode & S_IWUSR)
+ ret |= QFile::WriteUser;
+ if (mode & S_IXUSR)
+ ret |= QFile::ExeUser;
+ if (mode & S_IRGRP)
+ ret |= QFile::ReadGroup;
+ if (mode & S_IWGRP)
+ ret |= QFile::WriteGroup;
+ if (mode & S_IXGRP)
+ ret |= QFile::ExeGroup;
+ if (mode & S_IROTH)
+ ret |= QFile::ReadOther;
+ if (mode & S_IWOTH)
+ ret |= QFile::WriteOther;
+ if (mode & S_IXOTH)
+ ret |= QFile::ExeOther;
+ return ret;
+}
+
+struct LocalFileHeader
+{
+ uchar signature[4]; // 0x04034b50
+ uchar version_needed[2];
+ uchar general_purpose_bits[2];
+ uchar compression_method[2];
+ uchar last_mod_file[4];
+ uchar crc_32[4];
+ uchar compressed_size[4];
+ uchar uncompressed_size[4];
+ uchar file_name_length[2];
+ uchar extra_field_length[2];
+};
+
+struct DataDescriptor
+{
+ uchar crc_32[4];
+ uchar compressed_size[4];
+ uchar uncompressed_size[4];
+};
+
+struct CentralFileHeader
+{
+ uchar signature[4]; // 0x02014b50
+ uchar version_made[2];
+ uchar version_needed[2];
+ uchar general_purpose_bits[2];
+ uchar compression_method[2];
+ uchar last_mod_file[4];
+ uchar crc_32[4];
+ uchar compressed_size[4];
+ uchar uncompressed_size[4];
+ uchar file_name_length[2];
+ uchar extra_field_length[2];
+ uchar file_comment_length[2];
+ uchar disk_start[2];
+ uchar internal_file_attributes[2];
+ uchar external_file_attributes[4];
+ uchar offset_local_header[4];
+ LocalFileHeader toLocalHeader() const;
+};
+
+struct EndOfDirectory
+{
+ uchar signature[4]; // 0x06054b50
+ uchar this_disk[2];
+ uchar start_of_directory_disk[2];
+ uchar num_dir_entries_this_disk[2];
+ uchar num_dir_entries[2];
+ uchar directory_size[4];
+ uchar dir_start_offset[4];
+ uchar comment_length[2];
+};
+
+struct FileHeader
+{
+ CentralFileHeader h;
+ QByteArray file_name;
+ QByteArray extra_field;
+ QByteArray file_comment;
+};
+
+QZipReader::FileInfo::FileInfo()
+ : isDir(false), isFile(true), isSymLink(false), crc32(0), size(0)
+{
+}
+
+QZipReader::FileInfo::~FileInfo()
+{
+}
+
+QZipReader::FileInfo::FileInfo(const FileInfo &other)
+{
+ operator=(other);
+}
+
+QZipReader::FileInfo& QZipReader::FileInfo::operator=(const FileInfo &other)
+{
+ filePath = other.filePath;
+ isDir = other.isDir;
+ isFile = other.isFile;
+ isSymLink = other.isSymLink;
+ permissions = other.permissions;
+ crc32 = other.crc32;
+ size = other.size;
+ return *this;
+}
+
+class QZipPrivate
+{
+public:
+ QZipPrivate(QIODevice *device, bool ownDev)
+ : device(device), ownDevice(ownDev), dirtyFileTree(true), start_of_directory(0)
+ {
+ }
+
+ ~QZipPrivate()
+ {
+ if (ownDevice)
+ delete device;
+ }
+
+ void fillFileInfo(int index, QZipReader::FileInfo &fileInfo) const;
+
+ QIODevice *device;
+ bool ownDevice;
+ bool dirtyFileTree;
+ QList<FileHeader> fileHeaders;
+ QByteArray comment;
+ uint start_of_directory;
+};
+
+void QZipPrivate::fillFileInfo(int index, QZipReader::FileInfo &fileInfo) const
+{
+ FileHeader header = fileHeaders.at(index);
+ fileInfo.filePath = QString::fromLocal8Bit(header.file_name);
+ const quint32 mode = (qFromLittleEndian<quint32>(&header.h.external_file_attributes[0]) >> 16) & 0xFFFF;
+ fileInfo.isDir = S_ISDIR(mode);
+ fileInfo.isFile = S_ISREG(mode);
+ fileInfo.isSymLink = S_ISLNK(mode);
+ fileInfo.permissions = modeToPermissions(mode);
+ fileInfo.crc32 = readUInt(header.h.crc_32);
+ fileInfo.size = readUInt(header.h.uncompressed_size);
+}
+
+class QZipReaderPrivate : public QZipPrivate
+{
+public:
+ QZipReaderPrivate(QIODevice *device, bool ownDev)
+ : QZipPrivate(device, ownDev), status(QZipReader::NoError)
+ {
+ }
+
+ void scanFiles();
+
+ QZipReader::Status status;
+};
+
+class QZipWriterPrivate : public QZipPrivate
+{
+public:
+ QZipWriterPrivate(QIODevice *device, bool ownDev)
+ : QZipPrivate(device, ownDev),
+ status(QZipWriter::NoError),
+ permissions(QFile::ReadOwner | QFile::WriteOwner),
+ compressionPolicy(QZipWriter::AlwaysCompress)
+ {
+ }
+
+ QZipWriter::Status status;
+ QFile::Permissions permissions;
+ QZipWriter::CompressionPolicy compressionPolicy;
+
+ enum EntryType { Directory, File, Symlink };
+
+ void addEntry(EntryType type, const QString &fileName, const QByteArray &contents);
+};
+
+LocalFileHeader CentralFileHeader::toLocalHeader() const
+{
+ LocalFileHeader h;
+ writeUInt(h.signature, 0x04034b50);
+ copyUShort(h.version_needed, version_needed);
+ copyUShort(h.general_purpose_bits, general_purpose_bits);
+ copyUShort(h.compression_method, compression_method);
+ copyUInt(h.last_mod_file, last_mod_file);
+ copyUInt(h.crc_32, crc_32);
+ copyUInt(h.compressed_size, compressed_size);
+ copyUInt(h.uncompressed_size, uncompressed_size);
+ copyUShort(h.file_name_length, file_name_length);
+ copyUShort(h.extra_field_length, extra_field_length);
+ return h;
+}
+
+void QZipReaderPrivate::scanFiles()
+{
+ if (!dirtyFileTree)
+ return;
+
+ if (! (device->isOpen() || device->open(QIODevice::ReadOnly))) {
+ status = QZipReader::FileOpenError;
+ return;
+ }
+
+ if ((device->openMode() & QIODevice::ReadOnly) == 0) { // only read the index from readable files.
+ status = QZipReader::FileReadError;
+ return;
+ }
+
+ dirtyFileTree = false;
+ uchar tmp[4];
+ device->read((char *)tmp, 4);
+ if (readUInt(tmp) != 0x04034b50) {
+ qWarning() << "QZip: not a zip file!";
+ return;
+ }
+
+ // find EndOfDirectory header
+ int i = 0;
+ int start_of_directory = -1;
+ int num_dir_entries = 0;
+ EndOfDirectory eod;
+ while (start_of_directory == -1) {
+ int pos = device->size() - sizeof(EndOfDirectory) - i;
+ if (pos < 0 || i > 65535) {
+ qWarning() << "QZip: EndOfDirectory not found";
+ return;
+ }
+
+ device->seek(pos);
+ device->read((char *)&eod, sizeof(EndOfDirectory));
+ if (readUInt(eod.signature) == 0x06054b50)
+ break;
+ ++i;
+ }
+
+ // have the eod
+ start_of_directory = readUInt(eod.dir_start_offset);
+ num_dir_entries = readUShort(eod.num_dir_entries);
+ ZDEBUG("start_of_directory at %d, num_dir_entries=%d", start_of_directory, num_dir_entries);
+ int comment_length = readUShort(eod.comment_length);
+ if (comment_length != i)
+ qWarning() << "QZip: failed to parse zip file.";
+ comment = device->read(qMin(comment_length, i));
+
+
+ device->seek(start_of_directory);
+ for (i = 0; i < num_dir_entries; ++i) {
+ FileHeader header;
+ int read = device->read((char *) &header.h, sizeof(CentralFileHeader));
+ if (read < (int)sizeof(CentralFileHeader)) {
+ qWarning() << "QZip: Failed to read complete header, index may be incomplete";
+ break;
+ }
+ if (readUInt(header.h.signature) != 0x02014b50) {
+ qWarning() << "QZip: invalid header signature, index may be incomplete";
+ break;
+ }
+
+ int l = readUShort(header.h.file_name_length);
+ header.file_name = device->read(l);
+ if (header.file_name.length() != l) {
+ qWarning() << "QZip: Failed to read filename from zip index, index may be incomplete";
+ break;
+ }
+ l = readUShort(header.h.extra_field_length);
+ header.extra_field = device->read(l);
+ if (header.extra_field.length() != l) {
+ qWarning() << "QZip: Failed to read extra field in zip file, skipping file, index may be incomplete";
+ break;
+ }
+ l = readUShort(header.h.file_comment_length);
+ header.file_comment = device->read(l);
+ if (header.file_comment.length() != l) {
+ qWarning() << "QZip: Failed to read read file comment, index may be incomplete";
+ break;
+ }
+
+ ZDEBUG("found file '%s'", header.file_name.data());
+ fileHeaders.append(header);
+ }
+}
+
+void QZipWriterPrivate::addEntry(EntryType type, const QString &fileName, const QByteArray &contents/*, QFile::Permissions permissions, QZip::Method m*/)
+{
+#ifndef NDEBUG
+ static const char *entryTypes[] = {
+ "directory",
+ "file ",
+ "symlink " };
+ ZDEBUG() << "adding" << entryTypes[type] <<":" << fileName.toUtf8().data() << (type == 2 ? (" -> " + contents).constData() : "");
+#endif
+
+ if (! (device->isOpen() || device->open(QIODevice::WriteOnly))) {
+ status = QZipWriter::FileOpenError;
+ return;
+ }
+ device->seek(start_of_directory);
+
+ // don't compress small files
+ QZipWriter::CompressionPolicy compression = compressionPolicy;
+ if (compressionPolicy == QZipWriter::AutoCompress) {
+ if (contents.length() < 64)
+ compression = QZipWriter::NeverCompress;
+ else
+ compression = QZipWriter::AlwaysCompress;
+ }
+
+ FileHeader header;
+ memset(&header.h, 0, sizeof(CentralFileHeader));
+ writeUInt(header.h.signature, 0x02014b50);
+
+ writeUShort(header.h.version_needed, 0x14);
+ writeUInt(header.h.uncompressed_size, contents.length());
+ writeMSDosDate(header.h.last_mod_file, QDateTime::currentDateTime());
+ QByteArray data = contents;
+ if (compression == QZipWriter::AlwaysCompress) {
+ writeUShort(header.h.compression_method, 8);
+
+ ulong len = contents.length();
+ // shamelessly copied form zlib
+ len += (len >> 12) + (len >> 14) + 11;
+ int res;
+ do {
+ data.resize(len);
+ res = deflate((uchar*)data.data(), &len, (const uchar*)contents.constData(), contents.length());
+
+ switch (res) {
+ case Z_OK:
+ data.resize(len);
+ break;
+ case Z_MEM_ERROR:
+ qWarning("QZip: Z_MEM_ERROR: Not enough memory to compress file, skipping");
+ data.resize(0);
+ break;
+ case Z_BUF_ERROR:
+ len *= 2;
+ break;
+ }
+ } while (res == Z_BUF_ERROR);
+ }
+// TODO add a check if data.length() > contents.length(). Then try to store the original and revert the compression method to be uncompressed
+ writeUInt(header.h.compressed_size, data.length());
+ uint crc_32 = ::crc32(0, 0, 0);
+ crc_32 = ::crc32(crc_32, (const uchar *)contents.constData(), contents.length());
+ writeUInt(header.h.crc_32, crc_32);
+
+ header.file_name = fileName.toLocal8Bit();
+ if (header.file_name.size() > 0xffff) {
+ qWarning("QZip: Filename too long, chopping it to 65535 characters");
+ header.file_name = header.file_name.left(0xffff);
+ }
+ writeUShort(header.h.file_name_length, header.file_name.length());
+ //h.extra_field_length[2];
+
+ writeUShort(header.h.version_made, 3 << 8);
+ //uchar internal_file_attributes[2];
+ //uchar external_file_attributes[4];
+ quint32 mode = permissionsToMode(permissions);
+ switch (type) {
+ case File: mode |= S_IFREG; break;
+ case Directory: mode |= S_IFDIR; break;
+ case Symlink: mode |= S_IFLNK; break;
+ }
+ writeUInt(header.h.external_file_attributes, mode << 16);
+ writeUInt(header.h.offset_local_header, start_of_directory);
+
+
+ fileHeaders.append(header);
+
+ LocalFileHeader h = header.h.toLocalHeader();
+ device->write((const char *)&h, sizeof(LocalFileHeader));
+ device->write(header.file_name);
+ device->write(data);
+ start_of_directory = device->pos();
+ dirtyFileTree = true;
+}
+
+////////////////////////////// Reader
+
+/*!
+ \class QZipReader::FileInfo
+ \internal
+ Represents one entry in the zip table of contents.
+*/
+
+/*!
+ \variable FileInfo::filePath
+ The full filepath inside the archive.
+*/
+
+/*!
+ \variable FileInfo::isDir
+ A boolean type indicating if the entry is a directory.
+*/
+
+/*!
+ \variable FileInfo::isFile
+ A boolean type, if it is one this entry is a file.
+*/
+
+/*!
+ \variable FileInfo::isSymLink
+ A boolean type, if it is one this entry is symbolic link.
+*/
+
+/*!
+ \variable FileInfo::permissions
+ A list of flags for the permissions of this entry.
+*/
+
+/*!
+ \variable FileInfo::crc32
+ The calculated checksum as a crc32 type.
+*/
+
+/*!
+ \variable FileInfo::size
+ The total size of the unpacked content.
+*/
+
+/*!
+ \variable FileInfo::d
+ \internal
+ private pointer.
+*/
+
+/*!
+ \class QZipReader
+ \internal
+ \since 4.5
+
+ \brief the QZipReader class provides a way to inspect the contents of a zip
+ archive and extract individual files from it.
+
+ QZipReader can be used to read a zip archive either from a file or from any
+ device. An in-memory QBuffer for instance. The reader can be used to read
+ which files are in the archive using fileInfoList() and entryInfoAt() but
+ also to extract individual files using fileData() or even to extract all
+ files in the archive using extractAll()
+*/
+
+/*!
+ Create a new zip archive that operates on the \a fileName. The file will be
+ opened with the \a mode.
+*/
+QZipReader::QZipReader(const QString &archive, QIODevice::OpenMode mode)
+{
+ QFile *f = new QFile(archive);
+ f->open(mode);
+ QZipReader::Status status;
+ if (f->error() == QFile::NoError)
+ status = NoError;
+ else {
+ if (f->error() == QFile::ReadError)
+ status = FileReadError;
+ else if (f->error() == QFile::OpenError)
+ status = FileOpenError;
+ else if (f->error() == QFile::PermissionsError)
+ status = FilePermissionsError;
+ else
+ status = FileError;
+ }
+
+ d = new QZipReaderPrivate(f, /*ownDevice=*/true);
+ d->status = status;
+}
+
+/*!
+ Create a new zip archive that operates on the archive found in \a device.
+ You have to open the device previous to calling the constructor and only a
+ device that is readable will be scanned for zip filecontent.
+ */
+QZipReader::QZipReader(QIODevice *device)
+ : d(new QZipReaderPrivate(device, /*ownDevice=*/false))
+{
+ Q_ASSERT(device);
+}
+
+/*!
+ Desctructor
+*/
+QZipReader::~QZipReader()
+{
+ close();
+ delete d;
+}
+
+/*!
+ Returns true if the user can read the file; otherwise returns false.
+*/
+bool QZipReader::isReadable() const
+{
+ return d->device->isReadable();
+}
+
+/*!
+ Returns true if the file exists; otherwise returns false.
+*/
+bool QZipReader::exists() const
+{
+ QFile *f = qobject_cast<QFile*> (d->device);
+ if (f == 0)
+ return true;
+ return f->exists();
+}
+
+/*!
+ Returns the list of files the archive contains.
+*/
+QList<QZipReader::FileInfo> QZipReader::fileInfoList() const
+{
+ d->scanFiles();
+ QList<QZipReader::FileInfo> files;
+ for (int i = 0; d && i < d->fileHeaders.size(); ++i) {
+ QZipReader::FileInfo fi;
+ d->fillFileInfo(i, fi);
+ files.append(fi);
+ }
+ return files;
+
+}
+
+/*!
+ Return the number of items in the zip archive.
+*/
+int QZipReader::count() const
+{
+ d->scanFiles();
+ return d->fileHeaders.count();
+}
+
+/*!
+ Returns a FileInfo of an entry in the zipfile.
+ The \a index is the index into the directoy listing of the zipfile.
+
+ \sa fileInfoList()
+*/
+QZipReader::FileInfo QZipReader::entryInfoAt(int index) const
+{
+ d->scanFiles();
+ QZipReader::FileInfo fi;
+ d->fillFileInfo(index, fi);
+ return fi;
+}
+
+/*!
+ Fetch the file contents from the zip archive and return the uncompressed bytes.
+*/
+QByteArray QZipReader::fileData(const QString &fileName) const
+{
+ d->scanFiles();
+ int i;
+ for (i = 0; i < d->fileHeaders.size(); ++i) {
+ if (QString::fromLocal8Bit(d->fileHeaders.at(i).file_name) == fileName)
+ break;
+ }
+ if (i == d->fileHeaders.size())
+ return QByteArray();
+
+ FileHeader header = d->fileHeaders.at(i);
+
+ int compressed_size = readUInt(header.h.compressed_size);
+ int uncompressed_size = readUInt(header.h.uncompressed_size);
+ int start = readUInt(header.h.offset_local_header);
+ //qDebug("uncompressing file %d: local header at %d", i, start);
+
+ d->device->seek(start);
+ LocalFileHeader lh;
+ d->device->read((char *)&lh, sizeof(LocalFileHeader));
+ uint skip = readUShort(lh.file_name_length) + readUShort(lh.extra_field_length);
+ d->device->seek(d->device->pos() + skip);
+
+ int compression_method = readUShort(lh.compression_method);
+ //qDebug("file=%s: compressed_size=%d, uncompressed_size=%d", fileName.toLocal8Bit().data(), compressed_size, uncompressed_size);
+
+ //qDebug("file at %lld", d->device->pos());
+ QByteArray compressed = d->device->read(compressed_size);
+ if (compression_method == 0) {
+ // no compression
+ compressed.truncate(uncompressed_size);
+ return compressed;
+ } else if (compression_method == 8) {
+ // Deflate
+ //qDebug("compressed=%d", compressed.size());
+ compressed.truncate(compressed_size);
+ QByteArray baunzip;
+ ulong len = qMax(uncompressed_size, 1);
+ int res;
+ do {
+ baunzip.resize(len);
+ res = inflate((uchar*)baunzip.data(), &len,
+ (uchar*)compressed.constData(), compressed_size);
+
+ switch (res) {
+ case Z_OK:
+ if ((int)len != baunzip.size())
+ baunzip.resize(len);
+ break;
+ case Z_MEM_ERROR:
+ qWarning("QZip: Z_MEM_ERROR: Not enough memory");
+ break;
+ case Z_BUF_ERROR:
+ len *= 2;
+ break;
+ case Z_DATA_ERROR:
+ qWarning("QZip: Z_DATA_ERROR: Input data is corrupted");
+ break;
+ }
+ } while (res == Z_BUF_ERROR);
+ return baunzip;
+ }
+ qWarning() << "QZip: Unknown compression method";
+ return QByteArray();
+}
+
+/*!
+ Extracts the full contents of the zip file into \a destinationDir on
+ the local filesystem.
+ In case writing or linking a file fails, the extraction will be aborted.
+*/
+bool QZipReader::extractAll(const QString &destinationDir) const
+{
+ QDir baseDir(destinationDir);
+
+ // create directories first
+ QList<FileInfo> allFiles = fileInfoList();
+ foreach (FileInfo fi, allFiles) {
+ const QString absPath = destinationDir + QDir::separator() + fi.filePath;
+ if (fi.isDir) {
+ if (!baseDir.mkpath(fi.filePath))
+ return false;
+ if (!QFile::setPermissions(absPath, fi.permissions))
+ return false;
+ }
+ }
+
+ // set up symlinks
+ foreach (FileInfo fi, allFiles) {
+ const QString absPath = destinationDir + QDir::separator() + fi.filePath;
+ if (fi.isSymLink) {
+ QString destination = QFile::decodeName(fileData(fi.filePath));
+ if (destination.isEmpty())
+ return false;
+ QFileInfo linkFi(absPath);
+ if (!QFile::exists(linkFi.absolutePath()))
+ QDir::root().mkpath(linkFi.absolutePath());
+ if (!QFile::link(destination, absPath))
+ return false;
+ /* cannot change permission of links
+ if (!QFile::setPermissions(absPath, fi.permissions))
+ return false;
+ */
+ }
+ }
+
+ foreach (FileInfo fi, allFiles) {
+ const QString absPath = destinationDir + QDir::separator() + fi.filePath;
+ if (fi.isFile) {
+ QFile f(absPath);
+ if (!f.open(QIODevice::WriteOnly))
+ return false;
+ f.write(fileData(fi.filePath));
+ f.setPermissions(fi.permissions);
+ f.close();
+ }
+ }
+
+ return true;
+}
+
+/*!
+ \enum QZipReader::Status
+
+ The following status values are possible:
+
+ \value NoError No error occurred.
+ \value FileReadError An error occurred when reading from the file.
+ \value FileOpenError The file could not be opened.
+ \value FilePermissionsError The file could not be accessed.
+ \value FileError Another file error occurred.
+*/
+
+/*!
+ Returns a status code indicating the first error that was met by QZipReader,
+ or QZipReader::NoError if no error occurred.
+*/
+QZipReader::Status QZipReader::status() const
+{
+ return d->status;
+}
+
+/*!
+ Close the zip file.
+*/
+void QZipReader::close()
+{
+ d->device->close();
+}
+
+////////////////////////////// Writer
+
+/*!
+ \class QZipWriter
+ \internal
+ \since 4.5
+
+ \brief the QZipWriter class provides a way to create a new zip archive.
+
+ QZipWriter can be used to create a zip archive containing any number of files
+ and directories. The files in the archive will be compressed in a way that is
+ compatible with common zip reader applications.
+*/
+
+
+/*!
+ Create a new zip archive that operates on the \a archive filename. The file will
+ be opened with the \a mode.
+ \sa isValid()
+*/
+QZipWriter::QZipWriter(const QString &fileName, QIODevice::OpenMode mode)
+{
+ QFile *f = new QFile(fileName);
+ f->open(mode);
+ QZipWriter::Status status;
+ if (f->error() == QFile::NoError)
+ status = QZipWriter::NoError;
+ else {
+ if (f->error() == QFile::WriteError)
+ status = QZipWriter::FileWriteError;
+ else if (f->error() == QFile::OpenError)
+ status = QZipWriter::FileOpenError;
+ else if (f->error() == QFile::PermissionsError)
+ status = QZipWriter::FilePermissionsError;
+ else
+ status = QZipWriter::FileError;
+ }
+
+ d = new QZipWriterPrivate(f, /*ownDevice=*/true);
+ d->status = status;
+}
+
+/*!
+ Create a new zip archive that operates on the archive found in \a device.
+ You have to open the device previous to calling the constructor and
+ only a device that is readable will be scanned for zip filecontent.
+ */
+QZipWriter::QZipWriter(QIODevice *device)
+ : d(new QZipWriterPrivate(device, /*ownDevice=*/false))
+{
+ Q_ASSERT(device);
+}
+
+QZipWriter::~QZipWriter()
+{
+ close();
+ delete d;
+}
+
+/*!
+ Returns true if the user can write to the archive; otherwise returns false.
+*/
+bool QZipWriter::isWritable() const
+{
+ return d->device->isWritable();
+}
+
+/*!
+ Returns true if the file exists; otherwise returns false.
+*/
+bool QZipWriter::exists() const
+{
+ QFile *f = qobject_cast<QFile*> (d->device);
+ if (f == 0)
+ return true;
+ return f->exists();
+}
+
+/*!
+ \enum QZipWriter::Status
+
+ The following status values are possible:
+
+ \value NoError No error occurred.
+ \value FileWriteError An error occurred when writing to the device.
+ \value FileOpenError The file could not be opened.
+ \value FilePermissionsError The file could not be accessed.
+ \value FileError Another file error occurred.
+*/
+
+/*!
+ Returns a status code indicating the first error that was met by QZipWriter,
+ or QZipWriter::NoError if no error occurred.
+*/
+QZipWriter::Status QZipWriter::status() const
+{
+ return d->status;
+}
+
+/*!
+ \enum QZipWriter::CompressionPolicy
+
+ \value AlwaysCompress A file that is added is compressed.
+ \value NeverCompress A file that is added will be stored without changes.
+ \value AutoCompress A file that is added will be compressed only if that will give a smaller file.
+*/
+
+/*!
+ Sets the policy for compressing newly added files to the new \a policy.
+
+ \note the default policy is AlwaysCompress
+
+ \sa compressionPolicy()
+ \sa addFile()
+*/
+void QZipWriter::setCompressionPolicy(CompressionPolicy policy)
+{
+ d->compressionPolicy = policy;
+}
+
+/*!
+ Returns the currently set compression policy.
+ \sa setCompressionPolicy()
+ \sa addFile()
+*/
+QZipWriter::CompressionPolicy QZipWriter::compressionPolicy() const
+{
+ return d->compressionPolicy;
+}
+
+/*!
+ Sets the permissions that will be used for newly added files.
+
+ \note the default permissions are QFile::ReadOwner | QFile::WriteOwner.
+
+ \sa creationPermissions()
+ \sa addFile()
+*/
+void QZipWriter::setCreationPermissions(QFile::Permissions permissions)
+{
+ d->permissions = permissions;
+}
+
+/*!
+ Returns the currently set creation permissions.
+
+ \sa setCreationPermissions()
+ \sa addFile()
+*/
+QFile::Permissions QZipWriter::creationPermissions() const
+{
+ return d->permissions;
+}
+
+/*!
+ Add a file to the archive with \a data as the file contents.
+ The file will be stored in the archive using the \a fileName which
+ includes the full path in the archive.
+
+ The new file will get the file permissions based on the current
+ creationPermissions and it will be compressed using the zip compression
+ based on the current compression policy.
+
+ \sa setCreationPermissions()
+ \sa setCompressionPolicy()
+*/
+void QZipWriter::addFile(const QString &fileName, const QByteArray &data)
+{
+ d->addEntry(QZipWriterPrivate::File, fileName, data);
+}
+
+/*!
+ Add a file to the archive with \a device as the source of the contents.
+ The contents returned from QIODevice::readAll() will be used as the
+ filedata.
+ The file will be stored in the archive using the \a fileName which
+ includes the full path in the archive.
+*/
+void QZipWriter::addFile(const QString &fileName, QIODevice *device)
+{
+ Q_ASSERT(device);
+ QIODevice::OpenMode mode = device->openMode();
+ bool opened = false;
+ if ((mode & QIODevice::ReadOnly) == 0) {
+ opened = true;
+ if (! device->open(QIODevice::ReadOnly)) {
+ d->status = FileOpenError;
+ return;
+ }
+ }
+ d->addEntry(QZipWriterPrivate::File, fileName, device->readAll());
+ if (opened)
+ device->close();
+}
+
+/*!
+ Create a new directory in the archive with the specified \a dirName and
+ the \a permissions;
+*/
+void QZipWriter::addDirectory(const QString &dirName)
+{
+ QString name = dirName;
+ // separator is mandatory
+ if (!name.endsWith(QDir::separator()))
+ name.append(QDir::separator());
+ d->addEntry(QZipWriterPrivate::Directory, name, QByteArray());
+}
+
+/*!
+ Create a new symbolic link in the archive with the specified \a dirName
+ and the \a permissions;
+ A symbolic link contains the destination (relative) path and name.
+*/
+void QZipWriter::addSymLink(const QString &fileName, const QString &destination)
+{
+ d->addEntry(QZipWriterPrivate::Symlink, fileName, QFile::encodeName(destination));
+}
+
+/*!
+ Closes the zip file.
+*/
+void QZipWriter::close()
+{
+ if (!(d->device->openMode() & QIODevice::WriteOnly)) {
+ d->device->close();
+ return;
+ }
+
+ //qDebug("QZip::close writing directory, %d entries", d->fileHeaders.size());
+ d->device->seek(d->start_of_directory);
+ // write new directory
+ for (int i = 0; i < d->fileHeaders.size(); ++i) {
+ const FileHeader &header = d->fileHeaders.at(i);
+ d->device->write((const char *)&header.h, sizeof(CentralFileHeader));
+ d->device->write(header.file_name);
+ d->device->write(header.extra_field);
+ d->device->write(header.file_comment);
+ }
+ int dir_size = d->device->pos() - d->start_of_directory;
+ // write end of directory
+ EndOfDirectory eod;
+ memset(&eod, 0, sizeof(EndOfDirectory));
+ writeUInt(eod.signature, 0x06054b50);
+ //uchar this_disk[2];
+ //uchar start_of_directory_disk[2];
+ writeUShort(eod.num_dir_entries_this_disk, d->fileHeaders.size());
+ writeUShort(eod.num_dir_entries, d->fileHeaders.size());
+ writeUInt(eod.directory_size, dir_size);
+ writeUInt(eod.dir_start_offset, d->start_of_directory);
+ writeUShort(eod.comment_length, d->comment.length());
+
+ d->device->write((const char *)&eod, sizeof(EndOfDirectory));
+ d->device->write(d->comment);
+ d->device->close();
+}
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_TEXTODFWRITER
diff --git a/src/gui/text/qzipreader_p.h b/src/gui/text/qzipreader_p.h
new file mode 100644
index 0000000000..c2974a1a09
--- /dev/null
+++ b/src/gui/text/qzipreader_p.h
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QZIPREADER_H
+#define QZIPREADER_H
+
+#ifndef QT_NO_TEXTODFWRITER
+
+//
+// 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 <QtCore/qfile.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class QZipReaderPrivate;
+
+class Q_AUTOTEST_EXPORT QZipReader
+{
+public:
+ QZipReader(const QString &fileName, QIODevice::OpenMode mode = QIODevice::ReadOnly );
+
+ explicit QZipReader(QIODevice *device);
+ ~QZipReader();
+
+ bool isReadable() const;
+ bool exists() const;
+
+ struct Q_AUTOTEST_EXPORT FileInfo
+ {
+ FileInfo();
+ FileInfo(const FileInfo &other);
+ ~FileInfo();
+ FileInfo &operator=(const FileInfo &other);
+ QString filePath;
+ uint isDir : 1;
+ uint isFile : 1;
+ uint isSymLink : 1;
+ QFile::Permissions permissions;
+ uint crc32;
+ qint64 size;
+ void *d;
+ };
+
+ QList<FileInfo> fileInfoList() const;
+ int count() const;
+
+ FileInfo entryInfoAt(int index) const;
+ QByteArray fileData(const QString &fileName) const;
+ bool extractAll(const QString &destinationDir) const;
+
+ enum Status {
+ NoError,
+ FileReadError,
+ FileOpenError,
+ FilePermissionsError,
+ FileError
+ };
+
+ Status status() const;
+
+ void close();
+
+private:
+ QZipReaderPrivate *d;
+ Q_DISABLE_COPY(QZipReader)
+};
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_TEXTODFWRITER
+#endif // QZIPREADER_H
diff --git a/src/gui/text/qzipwriter_p.h b/src/gui/text/qzipwriter_p.h
new file mode 100644
index 0000000000..b5072b7286
--- /dev/null
+++ b/src/gui/text/qzipwriter_p.h
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef QZIPWRITER_H
+#define QZIPWRITER_H
+#ifndef QT_NO_TEXTODFWRITER
+
+//
+// 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 <QtCore/qstring.h>
+#include <QtCore/qfile.h>
+
+QT_BEGIN_NAMESPACE
+
+class QZipWriterPrivate;
+
+
+class Q_AUTOTEST_EXPORT QZipWriter
+{
+public:
+ QZipWriter(const QString &fileName, QIODevice::OpenMode mode = (QIODevice::WriteOnly | QIODevice::Truncate) );
+
+ explicit QZipWriter(QIODevice *device);
+ ~QZipWriter();
+
+ bool isWritable() const;
+ bool exists() const;
+
+ enum Status {
+ NoError,
+ FileWriteError,
+ FileOpenError,
+ FilePermissionsError,
+ FileError
+ };
+
+ Status status() const;
+
+ enum CompressionPolicy {
+ AlwaysCompress,
+ NeverCompress,
+ AutoCompress
+ };
+
+ void setCompressionPolicy(CompressionPolicy policy);
+ CompressionPolicy compressionPolicy() const;
+
+ void setCreationPermissions(QFile::Permissions permissions);
+ QFile::Permissions creationPermissions() const;
+
+ void addFile(const QString &fileName, const QByteArray &data);
+
+ void addFile(const QString &fileName, QIODevice *device);
+
+ void addDirectory(const QString &dirName);
+
+ void addSymLink(const QString &fileName, const QString &destination);
+
+ void close();
+private:
+ QZipWriterPrivate *d;
+ Q_DISABLE_COPY(QZipWriter)
+};
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_TEXTODFWRITER
+#endif // QZIPWRITER_H
diff --git a/src/gui/text/text.pri b/src/gui/text/text.pri
new file mode 100644
index 0000000000..fc33d4358f
--- /dev/null
+++ b/src/gui/text/text.pri
@@ -0,0 +1,177 @@
+# Qt kernel module
+
+HEADERS += \
+ text/qfont.h \
+ text/qfontdatabase.h \
+ text/qfontengine_p.h \
+ text/qfontengineglyphcache_p.h \
+ text/qfontinfo.h \
+ text/qfontmetrics.h \
+ text/qfont_p.h \
+ text/qfontsubset_p.h \
+ text/qtextcontrol_p.h \
+ text/qtextcontrol_p_p.h \
+ text/qtextengine_p.h \
+ text/qtextlayout.h \
+ text/qtextformat.h \
+ text/qtextformat_p.h \
+ text/qtextobject.h \
+ text/qtextobject_p.h \
+ text/qtextoption.h \
+ text/qfragmentmap_p.h \
+ text/qtextdocument.h \
+ text/qtextdocument_p.h \
+ text/qtexthtmlparser_p.h \
+ text/qabstracttextdocumentlayout.h \
+ text/qtextdocumentlayout_p.h \
+ text/qtextcursor.h \
+ text/qtextcursor_p.h \
+ text/qtextdocumentfragment.h \
+ text/qtextdocumentfragment_p.h \
+ text/qtextimagehandler_p.h \
+ text/qtexttable.h \
+ text/qtextlist.h \
+ text/qsyntaxhighlighter.h \
+ text/qtextdocumentwriter.h \
+ text/qcssparser_p.h \
+ text/qtexttable_p.h \
+ text/qzipreader_p.h \
+ text/qzipwriter_p.h \
+ text/qtextodfwriter_p.h
+
+SOURCES += \
+ text/qfont.cpp \
+ text/qfontengine.cpp \
+ text/qfontsubset.cpp \
+ text/qfontmetrics.cpp \
+ text/qfontdatabase.cpp \
+ text/qtextcontrol.cpp \
+ text/qtextengine.cpp \
+ text/qtextlayout.cpp \
+ text/qtextformat.cpp \
+ text/qtextobject.cpp \
+ text/qtextoption.cpp \
+ text/qfragmentmap.cpp \
+ text/qtextdocument.cpp \
+ text/qtextdocument_p.cpp \
+ text/qtexthtmlparser.cpp \
+ text/qabstracttextdocumentlayout.cpp \
+ text/qtextdocumentlayout.cpp \
+ text/qtextcursor.cpp \
+ text/qtextdocumentfragment.cpp \
+ text/qtextimagehandler.cpp \
+ text/qtexttable.cpp \
+ text/qtextlist.cpp \
+ text/qtextdocumentwriter.cpp \
+ text/qsyntaxhighlighter.cpp \
+ text/qcssparser.cpp \
+ text/qzip.cpp \
+ text/qtextodfwriter.cpp
+
+win32 {
+ SOURCES += \
+ text/qfont_win.cpp \
+ text/qfontengine_win.cpp
+ HEADERS += text/qfontengine_win_p.h
+}
+
+unix:x11 {
+ HEADERS += \
+ text/qfontengine_x11_p.h \
+ text/qfontengine_ft_p.h
+ SOURCES += \
+ text/qfont_x11.cpp \
+ text/qfontengine_x11.cpp \
+ text/qfontengine_ft.cpp
+}
+
+!embedded:!x11:mac {
+ SOURCES += \
+ text/qfont_mac.cpp
+ OBJECTIVE_SOURCES += text/qfontengine_mac.mm
+}
+
+embedded {
+ SOURCES += \
+ text/qfont_qws.cpp \
+ text/qfontengine_qws.cpp \
+ text/qfontengine_ft.cpp \
+ text/qfontengine_qpf.cpp \
+ text/qabstractfontengine_qws.cpp
+ HEADERS += \
+ text/qfontengine_ft_p.h \
+ text/qfontengine_qpf_p.h \
+ text/qabstractfontengine_qws.h \
+ text/qabstractfontengine_p.h
+ DEFINES += QT_NO_FONTCONFIG
+}
+
+contains(QT_CONFIG, freetype) {
+ SOURCES += \
+ ../3rdparty/freetype/builds/unix/ftsystem.c \
+ ../3rdparty/freetype/src/base/ftbase.c \
+ ../3rdparty/freetype/src/base/ftbbox.c \
+ ../3rdparty/freetype/src/base/ftdebug.c \
+ ../3rdparty/freetype/src/base/ftglyph.c \
+ ../3rdparty/freetype/src/base/ftinit.c \
+ ../3rdparty/freetype/src/base/ftmm.c \
+ ../3rdparty/freetype/src/base/fttype1.c \
+ ../3rdparty/freetype/src/base/ftbitmap.c\
+ ../3rdparty/freetype/src/bdf/bdf.c \
+ ../3rdparty/freetype/src/cache/ftcache.c \
+ ../3rdparty/freetype/src/cff/cff.c \
+ ../3rdparty/freetype/src/cid/type1cid.c \
+ ../3rdparty/freetype/src/gzip/ftgzip.c \
+ ../3rdparty/freetype/src/pcf/pcf.c \
+ ../3rdparty/freetype/src/pfr/pfr.c \
+ ../3rdparty/freetype/src/psaux/psaux.c \
+ ../3rdparty/freetype/src/pshinter/pshinter.c \
+ ../3rdparty/freetype/src/psnames/psmodule.c \
+ ../3rdparty/freetype/src/raster/raster.c \
+ ../3rdparty/freetype/src/sfnt/sfnt.c \
+ ../3rdparty/freetype/src/smooth/smooth.c \
+ ../3rdparty/freetype/src/truetype/truetype.c \
+ ../3rdparty/freetype/src/type1/type1.c \
+ ../3rdparty/freetype/src/type42/type42.c \
+ ../3rdparty/freetype/src/winfonts/winfnt.c \
+ ../3rdparty/freetype/src/lzw/ftlzw.c\
+ ../3rdparty/freetype/src/otvalid/otvalid.c\
+ ../3rdparty/freetype/src/otvalid/otvbase.c\
+ ../3rdparty/freetype/src/otvalid/otvgdef.c\
+ ../3rdparty/freetype/src/otvalid/otvjstf.c\
+ ../3rdparty/freetype/src/otvalid/otvcommn.c\
+ ../3rdparty/freetype/src/otvalid/otvgpos.c\
+ ../3rdparty/freetype/src/otvalid/otvgsub.c\
+ ../3rdparty/freetype/src/otvalid/otvmod.c\
+ ../3rdparty/freetype/src/autofit/afangles.c\
+ ../3rdparty/freetype/src/autofit/afglobal.c\
+ ../3rdparty/freetype/src/autofit/aflatin.c\
+ ../3rdparty/freetype/src/autofit/afmodule.c\
+ ../3rdparty/freetype/src/autofit/afdummy.c\
+ ../3rdparty/freetype/src/autofit/afhints.c\
+ ../3rdparty/freetype/src/autofit/afloader.c\
+ ../3rdparty/freetype/src/autofit/autofit.c
+
+ INCLUDEPATH += \
+ ../3rdparty/freetype/src \
+ ../3rdparty/freetype/include \
+ ../3rdparty/freetype/builds/unix
+
+ DEFINES += FT2_BUILD_LIBRARY FT_CONFIG_OPTION_SYSTEM_ZLIB
+
+ embedded:CONFIG += opentype
+} else:contains(QT_CONFIG, system-freetype) {
+ embedded:CONFIG += opentype
+ # pull in the proper freetype2 include directory
+ include($$QT_SOURCE_TREE/config.tests/unix/freetype/freetype.pri)
+ LIBS += -lfreetype
+} else {
+ DEFINES *= QT_NO_FREETYPE
+}
+
+contains(QT_CONFIG, fontconfig) {
+ CONFIG += opentype
+}
+
+DEFINES += QT_NO_OPENTYPE
+INCLUDEPATH += ../3rdparty/harfbuzz/src