| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). |
| ** All rights reserved. |
| ** Contact: Nokia Corporation (qt-info@nokia.com) |
| ** |
| ** This file is part of the QtGui module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** GNU Lesser General Public License Usage |
| ** This file may be used under the terms of the GNU Lesser General Public |
| ** License version 2.1 as published by the Free Software Foundation and |
| ** appearing in the file LICENSE.LGPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU Lesser |
| ** General Public License version 2.1 requirements will be met: |
| ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| ** |
| ** In addition, as a special exception, Nokia gives you certain additional |
| ** rights. These rights are described in the Nokia Qt LGPL Exception |
| ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| ** |
| ** 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. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $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; |
| inline bool isValid() const {return x != 100000 && y != 100000;} |
| }; |
| Q_DECLARE_TYPEINFO(glyph_metrics_t, Q_PRIMITIVE_TYPE); |
| |
| struct Q_AUTOTEST_EXPORT QScriptAnalysis |
| { |
| enum Flags { |
| None = 0, |
| Lowercase = 1, |
| Uppercase = 2, |
| SmallCaps = 3, |
| LineOrParagraphSeparator = 4, |
| Space = 5, |
| SpaceTabOrObject = Space, |
| Tab = 6, |
| TabOrObject = Tab, |
| Object = 7 |
| }; |
| unsigned short script : 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()); |
| QTextItemInt(const QGlyphLayout &g, QFont *font, const QChar *chars, int numChars, QFontEngine *fe, |
| 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; |
| void initWithScriptItem(const QScriptItem &si); |
| |
| QFixed descent; |
| QFixed ascent; |
| QFixed width; |
| |
| RenderFlags flags; |
| bool justified; |
| QTextCharFormat::UnderlineStyle underlineStyle; |
| const QTextCharFormat charFormat; |
| int num_chars; |
| const QChar *chars; |
| const unsigned short *logClusters; |
| const QFont *f; |
| |
| QGlyphLayout glyphs; |
| QFontEngine *fontEngine; |
| }; |
| |
| inline bool qIsControlChar(ushort uc) |
| { |
| return uc >= 0x200b && uc <= 0x206f |
| && (uc <= 0x200f /* ZW Space, ZWNJ, ZWJ, LRM and RLM */ |
| || (uc >= 0x2028 && uc <= 0x202f /* LS, PS, LRE, RLE, PDF, LRO, RLO, NNBSP */) |
| || uc >= 0x206a /* ISS, ASS, IAFS, AFS, NADS, NODS */); |
| } |
| |
| struct Q_AUTOTEST_EXPORT QScriptItem |
| { |
| inline QScriptItem() |
| : position(0), |
| num_glyphs(0), descent(-1), ascent(-1), leading(-1), width(-1), |
| glyph_data_offset(0) {} |
| inline QScriptItem(int p, const QScriptAnalysis &a) |
| : position(p), analysis(a), |
| num_glyphs(0), descent(-1), ascent(-1), leading(-1), width(-1), |
| glyph_data_offset(0) {} |
| |
| int position; |
| QScriptAnalysis analysis; |
| unsigned short num_glyphs; |
| QFixed descent; |
| QFixed ascent; |
| QFixed leading; |
| QFixed width; |
| int glyph_data_offset; |
| QFixed height() const { return ascent + descent + 1; } |
| }; |
| |
| |
| Q_DECLARE_TYPEINFO(QScriptItem, Q_MOVABLE_TYPE); |
| |
| typedef QVector<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), leadingIncluded(0) {} |
| QFixed descent; |
| QFixed ascent; |
| QFixed leading; |
| QFixed x; |
| QFixed y; |
| QFixed width; |
| QFixed textWidth; |
| QFixed textAdvance; |
| int from; |
| signed int length : 29; |
| mutable uint justified : 1; |
| mutable uint gridfitted : 1; |
| uint hasTrailingSpaces : 1; |
| uint leadingIncluded : 1; |
| QFixed height() const { return (ascent + descent).ceil() + 1 |
| + (leadingIncluded? qMax(QFixed(),leading) : QFixed()); } |
| QFixed base() const { return ascent |
| + (leadingIncluded ? qMax(QFixed(),leading) : QFixed()); } |
| void setDefaultHeight(QTextEngine *eng); |
| void operator+=(const QScriptLine &other); |
| }; |
| Q_DECLARE_TYPEINFO(QScriptLine, Q_PRIMITIVE_TYPE); |
| |
| |
| inline void QScriptLine::operator+=(const QScriptLine &other) |
| { |
| leading= qMax(leading + ascent, other.leading + other.ascent) - qMax(ascent, other.ascent); |
| descent = qMax(descent, other.descent); |
| ascent = qMax(ascent, other.ascent); |
| textWidth += other.textWidth; |
| length += other.length; |
| } |
| |
| typedef QVector<QScriptLine> QScriptLineArray; |
| |
| class QFontPrivate; |
| class QTextFormatCollection; |
| |
| class Q_GUI_EXPORT QTextEngine { |
| public: |
| enum LayoutState { |
| LayoutEmpty, |
| InLayout, |
| LayoutFailed, |
| }; |
| struct LayoutData { |
| LayoutData(const QString &str, void **stack_memory, int mem_size); |
| LayoutData(); |
| ~LayoutData(); |
| mutable QScriptItemArray items; |
| int allocated; |
| int available_glyphs; |
| void **memory; |
| unsigned short *logClustersPtr; |
| QGlyphLayout glyphLayout; |
| mutable int used; |
| uint hasBidi : 1; |
| uint layoutState : 2; |
| uint memory_on_stack : 1; |
| bool haveCharAttributes; |
| QString string; |
| bool reallocate(int totalGlyphs); |
| }; |
| |
| QTextEngine(LayoutData *data); |
| QTextEngine(); |
| QTextEngine(const QString &str, const QFont &f); |
| ~QTextEngine(); |
| |
| enum Mode { |
| WidthOnly = 0x07 |
| }; |
| |
| // keep in sync with QAbstractFontEngine::TextShapingFlag!! |
| enum ShaperFlag { |
| RightToLeft = 0x0001, |
| DesignMetrics = 0x0002, |
| GlyphIndicesOnly = 0x0004 |
| }; |
| Q_DECLARE_FLAGS(ShaperFlags, ShaperFlag) |
| |
| void invalidate(); |
| void clearLineData(); |
| |
| void validate() const; |
| void itemize() const; |
| |
| bool isRightToLeft() const; |
| static void bidiReorder(int numRuns, const quint8 *levels, int *visualOrder); |
| |
| const HB_CharAttributes *attributes() const; |
| |
| void shape(int item) const; |
| |
| void justify(const QScriptLine &si); |
| |
| QFixed width(int charFrom, int numChars) const; |
| glyph_metrics_t boundingBox(int from, int len) const; |
| glyph_metrics_t tightBoundingBox(int from, int len) const; |
| |
| int length(int item) const { |
| const QScriptItem &si = layoutData->items[item]; |
| int from = si.position; |
| item++; |
| return (item < layoutData->items.size() ? layoutData->items[item].position : layoutData->string.length()) - from; |
| } |
| int length(const QScriptItem *si) const { |
| int end; |
| if (si + 1 < layoutData->items.constData()+ layoutData->items.size()) |
| end = (si+1)->position; |
| else |
| end = layoutData->string.length(); |
| return end - si->position; |
| } |
| |
| QFontEngine *fontEngine(const QScriptItem &si, QFixed *ascent = 0, QFixed *descent = 0, QFixed *leading = 0) const; |
| QFont font(const QScriptItem &si) const; |
| inline QFont font() const { return fnt; } |
| |
| /** |
| * Returns a pointer to an array of log clusters, offset at the script item. |
| * Each item in the array is a unsigned short. For each character in the original string there is an entry in the table |
| * so there is a one to one correlation in indexes between the original text and the index in the logcluster. |
| * The value of each item is the position in the glyphs array. Multiple similar pointers in the logclusters array imply |
| * that one glyph is used for more than one character. |
| * \sa glyphs() |
| */ |
| inline unsigned short *logClusters(const QScriptItem *si) const |
| { return layoutData->logClustersPtr+si->position; } |
| /** |
| * Returns an array of QGlyphLayout items, offset at the script item. |
| * Each item in the array matches one glyph in the text, storing the advance, position etc. |
| * The returned item's length equals to the number of available glyphs. This may be more |
| * than what was actually shaped. |
| * \sa logClusters() |
| */ |
| inline QGlyphLayout availableGlyphs(const QScriptItem *si) const { |
| return layoutData->glyphLayout.mid(si->glyph_data_offset); |
| } |
| /** |
| * Returns an array of QGlyphLayout items, offset at the script item. |
| * Each item in the array matches one glyph in the text, storing the advance, position etc. |
| * The returned item's length equals to the number of shaped glyphs. |
| * \sa logClusters() |
| */ |
| inline QGlyphLayout shapedGlyphs(const QScriptItem *si) const { |
| return layoutData->glyphLayout.mid(si->glyph_data_offset, si->num_glyphs); |
| } |
| |
| inline bool ensureSpace(int nGlyphs) const { |
| if (layoutData->glyphLayout.numGlyphs - layoutData->used < nGlyphs) |
| return layoutData->reallocate((((layoutData->used + nGlyphs)*3/2 + 15) >> 4) << 4); |
| return true; |
| } |
| |
| void freeMemory(); |
| |
| int findItem(int strPos) const; |
| inline QTextFormatCollection *formats() const { |
| #ifdef QT_BUILD_COMPAT_LIB |
| return 0; // Compat should never reference this symbol |
| #else |
| return block.docHandle()->formatCollection(); |
| #endif |
| } |
| QTextCharFormat format(const QScriptItem *si) const; |
| inline QAbstractTextDocumentLayout *docLayout() const { |
| #ifdef QT_BUILD_COMPAT_LIB |
| return 0; // Compat should never reference this symbol |
| #else |
| return block.docHandle()->document()->documentLayout(); |
| #endif |
| } |
| int formatIndex(const QScriptItem *si) const; |
| |
| /// returns the width of tab at index (in the tabs array) with the tab-start at position x |
| QFixed calculateTabWidth(int index, QFixed x) const; |
| |
| mutable QScriptLineArray lines; |
| |
| struct FontEngineCache { |
| FontEngineCache(); |
| mutable QFontEngine *prevFontEngine; |
| mutable QFontEngine *prevScaledFontEngine; |
| mutable int prevScript; |
| mutable int prevPosition; |
| mutable int prevLength; |
| inline void reset() { |
| prevFontEngine = 0; |
| prevScaledFontEngine = 0; |
| prevScript = -1; |
| prevPosition = -1; |
| prevLength = -1; |
| } |
| }; |
| mutable FontEngineCache feCache; |
| |
| QString text; |
| QFont fnt; |
| QTextBlock block; |
| |
| QTextOption option; |
| |
| QFixed minWidth; |
| QFixed maxWidth; |
| QPointF position; |
| uint ignoreBidi : 1; |
| uint cacheGlyphs : 1; |
| uint stackEngine : 1; |
| uint forceJustification : 1; |
| |
| int *underlinePositions; |
| |
| mutable LayoutData *layoutData; |
| |
| inline bool hasFormats() const { return (block.docHandle() || specialData); } |
| |
| struct SpecialData { |
| int preeditPosition; |
| QString preeditText; |
| QList<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); |
| QFixed leadingSpaceWidth(const QScriptLine &line); |
| |
| int positionInLigature(const QScriptItem *si, int end, QFixed x, QFixed edge, int glyph_pos, bool cursorOnCharacter); |
| |
| private: |
| void setBoundary(int strPos) const; |
| void addRequiredBoundaries() const; |
| void shapeText(int item) const; |
| void shapeTextWithHarfbuzz(int item) const; |
| #if defined(Q_WS_WINCE) |
| void shapeTextWithCE(int item) const; |
| #endif |
| #if defined(Q_WS_MAC) |
| void shapeTextMac(int item) const; |
| #endif |
| void splitItem(int item, int pos) const; |
| |
| void resolveAdditionalFormats() const; |
| int getClusterLength(unsigned short *logClusters, const HB_CharAttributes *attributes, int from, int to, int glyph_pos, int *start); |
| }; |
| |
| 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 |