diff options
Diffstat (limited to 'src/gui/text')
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 0000000..4752de5 --- /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 0000000..3f2579a --- /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 0000000..3c6a1ea --- /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 0000000..6ad5775 --- /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 0000000..4c376cc --- /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 0000000..5674e17 --- /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 0000000..db1e781 --- /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 0000000..97a0aef --- /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 0000000..2f3fdb7 --- /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 0000000..43f5b1e --- /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 0000000..eec83b5 --- /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 0000000..8320f71 --- /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 0000000..4e8a7b7 --- /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 0000000..f07341d --- /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 0000000..5db5a68 --- /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 0000000..710792c --- /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 0000000..c3fc9f5 --- /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 0000000..eadb6ba --- /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 0000000..80ddbd5 --- /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 0000000..eb8a0cf --- /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 0000000..c9f5586 --- /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 0000000..15e626e --- /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 0000000..c4dffdf --- /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 0000000..45a25a4 --- /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 0000000..284904b --- /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 0000000..40d145a --- /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 0000000..e289aa9 --- /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 0000000..e9fcac4 --- /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 0000000..a9b87ff --- /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 0000000..d776329 --- /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 0000000..1996d44 --- /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 0000000..6f37e91 --- /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 0000000..0972b2b --- /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 0000000..eb00383 --- /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 0000000..8589cc6 --- /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 0000000..c1b0bb8 --- /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 0000000..88d0610 --- /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 "&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 "&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 "&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 "&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 0000000..95f71df --- /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 0000000..0d1a884 --- /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 0000000..3106ba8 --- /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 0000000..c3ce685 --- /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 0000000..737a717 --- /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 0000000..6fba213 --- /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 0000000..87648d5 --- /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 0000000..481dfd4 --- /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 0000000..f3d025c --- /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 0000000..e50540a --- /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 0000000..06955ce --- /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 0000000..c327b9f --- /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 0000000..97af323 --- /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 0000000..b16af21 --- /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 0000000..e84b324 --- /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("<"); + else if (plain.at(i) == QLatin1Char('>')) + rich += QLatin1String(">"); + else if (plain.at(i) == QLatin1Char('&')) + rich += QLatin1String("&"); + else + rich += plain.at(i); + } + return rich; +} + +/*! + \fn QString Qt::convertFromPlainText(const QString &plain, WhiteSpaceMode mode) + + Converts the plain text string \a plain to an HTML-formatted + paragraph while preserving most of its look. + + \a mode defines how whitespace is handled. + + This function is defined in the \c <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("<"); + else if (plain[i] == QLatin1Char('>')) + rich += QLatin1String(">"); + else if (plain[i] == QLatin1Char('&')) + rich += QLatin1String("&"); + 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 0000000..c337783 --- /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 0000000..320ecbf --- /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 0000000..25763e1 --- /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 0000000..21958a6 --- /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 0000000..269aca2 --- /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 0000000..743ed9d --- /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 0000000..c66d0c1 --- /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 0000000..d0206ab --- /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 0000000..08ee14e --- /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 0000000..53d17ca --- /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 0000000..80a5425 --- /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 0000000..d122317 --- /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 0000000..cf241fa --- /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 0000000..21bfc4d --- /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 0000000..0571d75 --- /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 0000000..115bc80 --- /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 0000000..b1f1b75 --- /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 0000000..a27b2f2 --- /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 0000000..c04f225 --- /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 0000000..f5426b5 --- /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 0000000..434d1ca --- /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, ®ion, 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 ¤t, 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 ¤t = 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(¤t); + } + const QScriptItem ¤t = 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 0000000..e5acb8e --- /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 0000000..e305027 --- /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 0000000..ab8d2d9 --- /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 0000000..1645a21 --- /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 0000000..5175441 --- /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 0000000..b00a16a --- /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 0000000..1edc3b8 --- /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 0000000..88e6b46 --- /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 0000000..e1b9844 --- /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 0000000..1c637a3 --- /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 0000000..375bb09 --- /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 0000000..76cd427 --- /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 0000000..1ba3a3f --- /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 0000000..c6c2e69 --- /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 0000000..c2974a1 --- /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 0000000..b5072b7 --- /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 0000000..fc33d43 --- /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 |