/**************************************************************************** ** ** ??? ** ** Copyright (C) 2005-2008 Trolltech ASA. All rights reserved. ** ** This file is part of the kernel module of the Qt GUI Toolkit. ** ** This file may be used under the terms of the GNU General ** Public License versions 2.0 or 3.0 as published by the Free ** Software Foundation and appearing in the files LICENSE.GPL2 ** and LICENSE.GPL3 included in the packaging of this file. ** Alternatively you may (at your option) use any later version ** of the GNU General Public License if such license has been ** publicly approved by Trolltech ASA (or its successors, if any) ** and the KDE Free Qt Foundation. ** ** Please review the following information to ensure GNU General ** Public Licensing requirements will be met: ** http://trolltech.com/products/qt/licenses/licensing/opensource/. ** If you are unsure which license is appropriate for your use, please ** review the following information: ** http://trolltech.com/products/qt/licenses/licensing/licensingoverview ** or contact the sales department at sales@trolltech.com. ** ** This file may be used under the terms of the Q Public License as ** defined by Trolltech ASA and appearing in the file LICENSE.QPL ** included in the packaging of this file. Licensees holding valid Qt ** Commercial licenses may use this file in accordance with the Qt ** Commercial License Agreement provided with the Software. ** ** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, ** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted ** herein. ** **********************************************************************/ #include "qtextlayout_p.h" #include "qtextengine_p.h" #include #include #include QRect QTextItem::rect() const { QScriptItem& si = engine->items[item]; return QRect( si.x, si.y, si.width, si.ascent+si.descent ); } int QTextItem::x() const { return engine->items[item].x; } int QTextItem::y() const { return engine->items[item].y; } int QTextItem::width() const { return engine->items[item].width; } int QTextItem::ascent() const { return engine->items[item].ascent; } int QTextItem::descent() const { return engine->items[item].descent; } void QTextItem::setWidth( int w ) { engine->items[item].width = w; } void QTextItem::setAscent( int a ) { engine->items[item].ascent = a; } void QTextItem::setDescent( int d ) { engine->items[item].descent = d; } int QTextItem::from() const { return engine->items[item].position; } int QTextItem::length() const { return engine->length(item); } int QTextItem::cursorToX( int *cPos, Edge edge ) const { int pos = *cPos; QScriptItem *si = &engine->items[item]; engine->shape( item ); advance_t *advances = engine->advances( si ); GlyphAttributes *glyphAttributes = engine->glyphAttributes( si ); unsigned short *logClusters = engine->logClusters( si ); int l = engine->length( item ); 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 && !glyphAttributes[glyph_pos].clusterStart ) glyph_pos++; } int x = 0; bool reverse = engine->items[item].analysis.bidiLevel % 2; if ( reverse ) { for ( int i = si->num_glyphs-1; i >= glyph_pos; i-- ) x += advances[i]; } else { for ( int i = 0; i < glyph_pos; i++ ) x += advances[i]; } // qDebug("cursorToX: pos=%d, gpos=%d x=%d", pos, glyph_pos, x ); *cPos = pos; return x; } int QTextItem::xToCursor( int x, CursorPosition cpos ) const { QScriptItem *si = &engine->items[item]; engine->shape( item ); advance_t *advances = engine->advances( si ); unsigned short *logClusters = engine->logClusters( si ); int l = engine->length( item ); bool reverse = si->analysis.bidiLevel % 2; if ( x < 0 ) return reverse ? l : 0; if ( reverse ) { int width = 0; for ( int i = 0; i < si->num_glyphs; i++ ) { width += advances[i]; } x = -x + width; } int cp_before = 0; int cp_after = 0; int x_before = 0; int x_after = 0; int lastCluster = 0; for ( int i = 1; i <= l; i++ ) { int newCluster = i < l ? logClusters[i] : si->num_glyphs; if ( newCluster != lastCluster ) { // calculate cluster width cp_before = cp_after; x_before = x_after; cp_after = i; for ( int j = lastCluster; j < newCluster; j++ ) x_after += advances[j]; // qDebug("cluster boundary: lastCluster=%d, newCluster=%d, x_before=%d, x_after=%d", // lastCluster, newCluster, x_before, x_after ); if ( x_after > x ) break; lastCluster = newCluster; } } bool before = ( cpos == OnCharacters || (x - x_before) < (x_after - x) ); // qDebug("got cursor position for %d: %d/%d, x_ba=%d/%d using %d", // x, cp_before,cp_after, x_before, x_after, before ? cp_before : cp_after ); return before ? cp_before : cp_after; } bool QTextItem::isRightToLeft() const { return (engine->items[item].analysis.bidiLevel % 2); } bool QTextItem::isObject() const { return engine->items[item].isObject; } bool QTextItem::isSpace() const { return engine->items[item].isSpace; } bool QTextItem::isTab() const { return engine->items[item].isTab; } QTextLayout::QTextLayout() :d(0) {} QTextLayout::QTextLayout( const QString& string, QPainter *p ) { QFontPrivate *f = p ? ( p->pfont ? p->pfont->d : p->cfont.d ) : QApplication::font().d; d = new QTextEngine( (string.isNull() ? (const QString&)QString::fromLatin1("") : string), f ); } QTextLayout::QTextLayout( const QString& string, const QFont& fnt ) { d = new QTextEngine( (string.isNull() ? (const QString&)QString::fromLatin1("") : string), fnt.d ); } QTextLayout::~QTextLayout() { delete d; } void QTextLayout::setText( const QString& string, const QFont& fnt ) { delete d; d = new QTextEngine( (string.isNull() ? (const QString&)QString::fromLatin1("") : string), fnt.d ); } /* add an additional item boundary eg. for style change */ void QTextLayout::setBoundary( int strPos ) { if ( strPos <= 0 || strPos >= (int)d->string.length() ) return; int itemToSplit = 0; while ( itemToSplit < d->items.size() && d->items[itemToSplit].position <= strPos ) itemToSplit++; itemToSplit--; if ( d->items[itemToSplit].position == strPos ) { // already a split at the requested position return; } d->splitItem( itemToSplit, strPos - d->items[itemToSplit].position ); } int QTextLayout::numItems() const { return d->items.size(); } QTextItem QTextLayout::itemAt( int i ) const { return QTextItem( i, d ); } QTextItem QTextLayout::findItem( int strPos ) const { if ( strPos == 0 && d->items.size() ) return QTextItem( 0, d ); // ## TODO use bsearch for ( int i = d->items.size()-1; i >= 0; --i ) { if ( d->items[i].position < strPos ) return QTextItem( i, d ); } return QTextItem(); } void QTextLayout::beginLayout( QTextLayout::LayoutMode m ) { d->items.clear(); QTextEngine::Mode mode = QTextEngine::Full; if (m == NoBidi) mode = QTextEngine::NoBidi; else if (m == SingleLine) mode = QTextEngine::SingleLine; d->itemize( mode ); d->currentItem = 0; d->firstItemInLine = -1; } void QTextLayout::beginLine( int width ) { d->lineWidth = width; d->widthUsed = 0; d->firstItemInLine = -1; } bool QTextLayout::atEnd() const { return d->currentItem >= d->items.size(); } QTextItem QTextLayout::nextItem() { d->currentItem++; if ( d->currentItem >= d->items.size() ) return QTextItem(); d->shape( d->currentItem ); return QTextItem( d->currentItem, d ); } QTextItem QTextLayout::currentItem() { if ( d->currentItem >= d->items.size() ) return QTextItem(); d->shape( d->currentItem ); return QTextItem( d->currentItem, d ); } /* ## maybe also currentItem() */ void QTextLayout::setLineWidth( int newWidth ) { d->lineWidth = newWidth; } int QTextLayout::lineWidth() const { return d->lineWidth; } int QTextLayout::widthUsed() const { return d->widthUsed; } int QTextLayout::availableWidth() const { return d->lineWidth - d->widthUsed; } /* returns true if completely added */ QTextLayout::Result QTextLayout::addCurrentItem() { if ( d->firstItemInLine == -1 ) d->firstItemInLine = d->currentItem; QScriptItem ¤t = d->items[d->currentItem]; d->shape( d->currentItem ); d->widthUsed += current.width; // qDebug("trying to add item %d with width %d, remaining %d", d->currentItem, current.width, d->lineWidth-d->widthUsed ); d->currentItem++; return (d->widthUsed <= d->lineWidth || (d->currentItem < d->items.size() && d->items[d->currentItem].isSpace)) ? Ok : LineFull; } QTextLayout::Result QTextLayout::endLine( int x, int y, int alignment, int *ascent, int *descent, int *lineLeft, int *lineRight ) { int available = d->lineWidth; int numRuns = 0; int numSpaceItems = 0; Q_UINT8 _levels[128]; int _visual[128]; Q_UINT8 *levels = _levels; int *visual = _visual; int i; QTextLayout::Result result = LineEmpty; // qDebug("endLine x=%d, y=%d, first=%d, current=%d lw=%d wu=%d", x, y, d->firstItemInLine, d->currentItem, d->lineWidth, d->widthUsed ); int width_nobreak_found = d->widthUsed; if ( d->firstItemInLine == -1 ) goto end; if ( !(alignment & (Qt::SingleLine|Qt::IncludeTrailingSpaces)) && d->currentItem > d->firstItemInLine && d->items[d->currentItem-1].isSpace ) { int i = d->currentItem-1; while ( i > d->firstItemInLine && d->items[i].isSpace ) { numSpaceItems++; d->widthUsed -= d->items[i--].width; } } if ( (alignment & (Qt::WordBreak|Qt::BreakAnywhere)) && d->widthUsed > d->lineWidth ) { // find linebreak // even though we removed trailing spaces the line was too wide. We'll have to break at an earlier // position. To not confuse the layouting below, reset the number of space items numSpaceItems = 0; bool breakany = alignment & Qt::BreakAnywhere; const QCharAttributes *attrs = d->attributes(); int w = 0; int itemWidth = 0; int breakItem = d->firstItemInLine; int breakPosition = -1; #if 0 // we iterate backwards or forward depending on what we guess is closer if ( d->widthUsed - d->lineWidth < d->lineWidth ) { // backwards search should be faster } else #endif { int tmpWidth = 0; int swidth = 0; // forward search is probably faster for ( int i = d->firstItemInLine; i < d->currentItem; i++ ) { const QScriptItem *si = &d->items[i]; int length = d->length( i ); const QCharAttributes *itemAttrs = attrs + si->position; advance_t *advances = d->advances( si ); unsigned short *logClusters = d->logClusters( si ); int lastGlyph = 0; int tmpItemWidth = 0; // qDebug("looking for break in item %d, isSpace=%d", i, si->isSpace ); if(si->isSpace && !(alignment & (Qt::SingleLine|Qt::IncludeTrailingSpaces))) { swidth += si->width; } else { tmpWidth += swidth; swidth = 0; for ( int pos = 0; pos < length; pos++ ) { // qDebug("advance=%d, w=%d, tmpWidth=%d, softbreak=%d, whitespace=%d", // *advances, w, tmpWidth, itemAttrs->softBreak, itemAttrs->whiteSpace ); int glyph = logClusters[pos]; if ( lastGlyph != glyph ) { while ( lastGlyph < glyph ) tmpItemWidth += advances[lastGlyph++]; if ( breakPosition != -1 && w + tmpWidth + tmpItemWidth > d->lineWidth ) { // qDebug("found break at w=%d, tmpWidth=%d, tmpItemWidth=%d", w, tmpWidth, tmpItemWidth); d->widthUsed = w; goto found; } } if ( (itemAttrs->softBreak || ( breakany && itemAttrs->charStop ) ) && (i != d->firstItemInLine || pos != 0) ) { if ( breakItem != i ) itemWidth = 0; if (itemAttrs->softBreak) breakany = FALSE; breakItem = i; breakPosition = pos; // qDebug("found possible break at item %d, position %d (absolute=%d), w=%d, tmpWidth=%d, tmpItemWidth=%d", breakItem, breakPosition, d->items[breakItem].position+breakPosition, w, tmpWidth, tmpItemWidth); w += tmpWidth + tmpItemWidth; itemWidth += tmpItemWidth; tmpWidth = 0; tmpItemWidth = 0; } itemAttrs++; } while ( lastGlyph < si->num_glyphs ) tmpItemWidth += advances[lastGlyph++]; tmpWidth += tmpItemWidth; if ( w + tmpWidth > d->lineWidth ) { d->widthUsed = w; goto found; } } } } found: // no valid break point found if ( breakPosition == -1 ) { d->widthUsed = width_nobreak_found; goto nobreak; } // qDebug("linebreak at item %d, position %d, wu=%d", breakItem, breakPosition, d->widthUsed ); // split the line if ( breakPosition > 0 ) { // int length = d->length( breakItem ); // qDebug("splitting item, itemWidth=%d", itemWidth); // not a full item, need to break d->splitItem( breakItem, breakPosition ); d->currentItem = breakItem+1; } else { d->currentItem = breakItem; } } result = Ok; nobreak: // position the objects in the line available -= d->widthUsed; numRuns = d->currentItem - d->firstItemInLine - numSpaceItems; if ( numRuns > 127 ) { levels = new Q_UINT8[numRuns]; visual = new int[numRuns]; } // qDebug("reordering %d runs, numSpaceItems=%d", numRuns, numSpaceItems ); for ( i = 0; i < numRuns; i++ ) { levels[i] = d->items[i+d->firstItemInLine].analysis.bidiLevel; // qDebug(" level = %d", d->items[i+d->firstItemInLine].analysis.bidiLevel ); } d->bidiReorder( numRuns, levels, visual ); end: // ### FIXME if ( alignment & Qt::AlignJustify ) { // #### justify items alignment = Qt::AlignAuto; } if ( (alignment & Qt::AlignHorizontal_Mask) == Qt::AlignAuto ) alignment = Qt::AlignLeft; if ( alignment & Qt::AlignRight ) x += available; else if ( alignment & Qt::AlignHCenter ) x += available/2; int asc = ascent ? *ascent : 0; int desc = descent ? *descent : 0; for ( i = 0; i < numRuns; i++ ) { QScriptItem &si = d->items[d->firstItemInLine+visual[i]]; asc = QMAX( asc, si.ascent ); desc = QMAX( desc, si.descent ); } int left = x; for ( i = 0; i < numRuns; i++ ) { QScriptItem &si = d->items[d->firstItemInLine+visual[i]]; // qDebug("positioning item %d with width %d (from=%d/length=%d) at %d", d->firstItemInLine+visual[i], si.width, si.position, // d->length(d->firstItemInLine+visual[i]), x ); si.x = x; si.y = y + asc; x += si.width; } int right = x; if ( numSpaceItems ) { if ( d->items[d->firstItemInLine+numRuns].analysis.bidiLevel % 2 ) { x = left; for ( i = 0; i < numSpaceItems; i++ ) { QScriptItem &si = d->items[d->firstItemInLine + numRuns + i]; x -= si.width; si.x = x; si.y = y + asc; } } else { for ( i = 0; i < numSpaceItems; i++ ) { QScriptItem &si = d->items[d->firstItemInLine + numRuns + i]; si.x = x; si.y = y + asc; x += si.width; } } } if ( lineLeft ) *lineLeft = left; if ( lineRight ) *lineRight = right; if ( ascent ) *ascent = asc; if ( descent ) *descent = desc; if (levels != _levels) delete []levels; if (visual != _visual) delete []visual; return result; } void QTextLayout::endLayout() { // nothing to do currently } int QTextLayout::nextCursorPosition( int oldPos, CursorMode mode ) const { // qDebug("looking for next cursor pos for %d", oldPos ); const QCharAttributes *attributes = d->attributes(); int len = d->string.length(); if ( oldPos >= len ) return oldPos; oldPos++; if ( mode == SkipCharacters ) { while ( oldPos < len && !attributes[oldPos].charStop ) oldPos++; } else { while ( oldPos < len && !attributes[oldPos].wordStop && !attributes[oldPos-1].whiteSpace ) oldPos++; } // qDebug(" -> %d", oldPos ); return oldPos; } int QTextLayout::previousCursorPosition( int oldPos, CursorMode mode ) const { // qDebug("looking for previous cursor pos for %d", oldPos ); const QCharAttributes *attributes = d->attributes(); if ( oldPos <= 0 ) return 0; oldPos--; if ( mode == SkipCharacters ) { while ( oldPos && !attributes[oldPos].charStop ) oldPos--; } else { while ( oldPos && !attributes[oldPos].wordStop && !attributes[oldPos-1].whiteSpace ) oldPos--; } // qDebug(" -> %d", oldPos ); return oldPos; } bool QTextLayout::validCursorPosition( int pos ) const { const QCharAttributes *attributes = d->attributes(); if ( pos < 0 || pos > (int)d->string.length() ) return FALSE; return attributes[pos].charStop; } void QTextLayout::setDirection(QChar::Direction dir) { d->direction = dir; }