/**************************************************************************** ** $Id: KoComplexText.cpp 504496 2006-02-01 11:16:49Z dfaure $ ** ** Implementation of some internal classes ** ** Created : ** ** Copyright (C) 2001 Trolltech AS. All rights reserved. ** ** This file is part of the kernel module of the TQt GUI Toolkit. ** ** This file may be distributed under the terms of the Q Public License ** as defined by Trolltech AS of Norway and appearing in the file ** LICENSE.TQPL included in the packaging of this file. ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. ** ** Licensees holding valid TQt Enterprise Edition or TQt Professional Edition ** licenses may use this file in accordance with the TQt Commercial License ** Agreement provided with the Software. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for ** information about TQt Commercial License Agreements. ** See http://www.trolltech.com/qpl/ for TQPL licensing information. ** See http://www.trolltech.com/gpl/ for GPL licensing information. ** ** Contact info@trolltech.com if any conditions of this licensing are ** not clear to you. ** **********************************************************************/ #ifndef TQT_NO_COMPLEXTEXT #include "KoRichText.h" //#include "tqfontdata_p.h" #include "tqfontmetrics.h" #include "tqrect.h" #include // ----------------------------------------------------- /* a small helper class used internally to resolve Bidi embedding levels. Each line of text caches the embedding level at the start of the line for faster relayouting */ KoBidiContext::KoBidiContext( uchar l, TQChar::Direction e, KoBidiContext *p, bool o ) : level(l) , override(o), dir(e) { if ( p ) p->ref(); parent = p; count = 0; } KoBidiContext::~KoBidiContext() { if( parent && parent->deref() ) delete parent; } static TQChar *tqshapeBuffer = 0; static int tqshapeBufSize = 0; /* Arabic shaping obeys a number of rules according to the joining classes (see Unicode book, section on arabic). Each tqunicode char has a joining class (right, dual (left&right), center (joincausing) or transparent). transparent joining is not encoded in TQChar::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 two functions defined in this class do shaping in visual and logical order. For logical order just replace right with previous and left with next in the above rules ;-) */ /* Two small helper functions for arabic shaping. They get the next tqshape causing character on either side of the char in question. Implements rule R1. leftChar() returns true if the char to the left is a left join-causing char rightChar() returns true if the char to the right is a right join-causing char */ static inline const TQChar *prevChar( const TQString &str, int pos ) { //kdDebug() << "leftChar: pos=" << pos << endl; pos--; const TQChar *ch = str.tqunicode() + pos; while( pos > -1 ) { if( !ch->isMark() ) return ch; pos--; ch--; } return &TQChar::replacement; } static inline const TQChar *nextChar( const TQString &str, int pos) { pos++; int len = str.length(); const TQChar *ch = str.tqunicode() + pos; while( pos < len ) { //kdDebug() << "rightChar: " << pos << " isLetter=" << ch.isLetter() << ", joining=" << ch.joining() << endl; if( !ch->isMark() ) return ch; // assume it's a transparent char, this might not be 100% correct pos++; ch++; } return &TQChar::replacement; } static inline bool prevVisualCharJoins( const TQString &str, int pos) { return ( prevChar( str, pos )->joining() != TQChar::OtherJoining ); } static inline bool nextVisualCharJoins( const TQString &str, int pos) { TQChar::Joining join = nextChar( str, pos )->joining(); return ( join == TQChar::Dual || join == TQChar::Center ); } KoComplexText::Shape KoComplexText::glyphVariant( const TQString &str, int pos) { // ignores L1 - L3, done in the codec TQChar::Joining joining = str[pos].joining(); //kdDebug() << "checking " << str[pos].tqunicode() << ", joining=" << joining << endl; switch ( joining ) { case TQChar::OtherJoining: case TQChar::Center: // these don't change tqshape return XIsolated; case TQChar::Right: // only rule R2 applies if( nextVisualCharJoins( str, pos ) ) return XFinal; return XIsolated; case TQChar::Dual: bool right = nextVisualCharJoins( str, pos ); bool left = prevVisualCharJoins( str, pos ); //kdDebug() << "dual: right=" << right << ", left=" << left << endl; if( right && left ) return XMedial; else if ( right ) return XFinal; else if ( left ) return XInitial; else return XIsolated; } return XIsolated; } /* and the same thing for logical ordering :) */ static inline bool prevLogicalCharJoins( const TQString &str, int pos) { return ( nextChar( str, pos )->joining() != TQChar::OtherJoining ); } static inline bool nextLogicalCharJoins( const TQString &str, int pos) { TQChar::Joining join = prevChar( str, pos )->joining(); return ( join == TQChar::Dual || join == TQChar::Center ); } KoComplexText::Shape KoComplexText::glyphVariantLogical( const TQString &str, int pos) { // ignores L1 - L3, ligatures are job of the codec TQChar::Joining joining = str[pos].joining(); //kdDebug() << "checking " << str[pos].tqunicode() << ", joining=" << joining << endl; switch ( joining ) { case TQChar::OtherJoining: case TQChar::Center: // these don't change tqshape return XIsolated; case TQChar::Right: // only rule R2 applies if( nextLogicalCharJoins( str, pos ) ) return XFinal; return XIsolated; case TQChar::Dual: bool right = nextLogicalCharJoins( str, pos ); bool left = prevLogicalCharJoins( str, pos ); //kdDebug() << "dual: right=" << right << ", left=" << left << endl; if( right && left ) return XMedial; else if ( right ) return XFinal; else if ( left ) return XInitial; else return XIsolated; } return XIsolated; } // ------------------------------------------------------------- // The tqunicode to tqunicode shaping codec. // does only presentation forms B at the moment, but that should be enough for // simple display static const ushort arabicUnicodeMapping[256][2] = { // base of tqshaped forms, and number-1 of them ( 0 for non shaping, // 1 for right binding and 3 for dual binding { 0x0600, 0 }, // 0x600 { 0x0601, 0 }, // 0x601 { 0x0602, 0 }, // 0x602 { 0x0603, 0 }, // 0x603 { 0x0604, 0 }, // 0x604 { 0x0605, 0 }, // 0x605 { 0x0606, 0 }, // 0x606 { 0x0607, 0 }, // 0x607 { 0x0608, 0 }, // 0x608 { 0x0609, 0 }, // 0x609 { 0x060a, 0 }, // 0x60a { 0x060b, 0 }, // 0x60b { 0x060c, 0 }, // 0x60c Arabic comma { 0x060d, 0 }, // 0x60d { 0x060e, 0 }, // 0x60e { 0x060f, 0 }, // 0x60f { 0x0610, 0 }, // 0x610 { 0x0611, 0 }, // 0x611 { 0x0612, 0 }, // 0x612 { 0x0613, 0 }, // 0x613 { 0x0614, 0 }, // 0x614 { 0x0615, 0 }, // 0x615 { 0x0616, 0 }, // 0x616 { 0x0617, 0 }, // 0x617 { 0x0618, 0 }, // 0x618 { 0x0619, 0 }, // 0x619 { 0x061a, 0 }, // 0x61a { 0x061b, 0 }, // 0x61b Arabic semicolon { 0x061c, 0 }, // 0x61c { 0x061d, 0 }, // 0x61d { 0x061e, 0 }, // 0x61e { 0x061f, 0 }, // 0x61f Arabic question mark { 0x0620, 0 }, // 0x620 { 0xfe80, 0 }, // 0x621 Hamza { 0xfe81, 1 }, // 0x622 R Alef with Madda above { 0xfe83, 1 }, // 0x623 R Alef with Hamza above { 0xfe85, 1 }, // 0x624 R Waw with Hamza above { 0xfe87, 1 }, // 0x625 R Alef with Hamza below { 0xfe89, 3 }, // 0x626 D Yeh with Hamza above { 0xfe8d, 1 }, // 0x627 R Alef { 0xfe8f, 3 }, // 0x628 D Beh { 0xfe93, 1 }, // 0x629 R Teh Marbuta { 0xfe95, 3 }, // 0x62a D Teh { 0xfe99, 3 }, // 0x62b D Theh { 0xfe9d, 3 }, // 0x62c D Jeem { 0xfea1, 3 }, // 0x62d D Hah { 0xfea5, 3 }, // 0x62e D Khah { 0xfea9, 1 }, // 0x62f R Dal { 0xfeab, 1 }, // 0x630 R Thal { 0xfead, 1 }, // 0x631 R Reh { 0xfeaf, 1 }, // 0x632 R Zain { 0xfeb1, 1 }, // 0x633 D Seen { 0xfeb5, 3 }, // 0x634 D Sheen { 0xfeb9, 3 }, // 0x635 D Sad { 0xfebd, 3 }, // 0x636 D Dad { 0xfec1, 3 }, // 0x637 D Tah { 0xfec5, 3 }, // 0x638 D Zah { 0xfec9, 3 }, // 0x639 D Ain { 0xfecd, 3 }, // 0x63a D Ghain { 0x063b, 0 }, // 0x63b { 0x063c, 0 }, // 0x63c { 0x063d, 0 }, // 0x63d { 0x063e, 0 }, // 0x63e { 0x063f, 0 }, // 0x63f { 0x0640, 0 }, // 0x640 C Tatweel { 0xfed1, 3 }, // 0x641 D Feh { 0xfed5, 3 }, // 0x642 D Qaf { 0xfed9, 3 }, // 0x643 D Kaf { 0xfedd, 3 }, // 0x644 D Lam { 0xfee1, 3 }, // 0x645 D Meem { 0xfee5, 3 }, // 0x646 D Noon { 0xfee9, 3 }, // 0x647 D Heh { 0xfeed, 1 }, // 0x648 R Waw { 0xfeef, 1 }, // 0x649 R Alef Maksura // ### Dual according to newest arabicshaping.txt { 0xfef1, 3 }, // 0x64a D Yeh { 0x064b, 0 }, // 0x64b Mark Fathatan { 0x064c, 0 }, // 0x64c Mark Dammatan { 0x064d, 0 }, // 0x64d Mark Kasratan { 0x064e, 0 }, // 0x64e Mark Fatha { 0x064f, 0 }, // 0x64f Mark Damma { 0x0650, 0 }, // 0x650 Mark Kasra { 0x0651, 0 }, // 0x651 Mark Shadda { 0x0652, 0 }, // 0x652 Mark Sukan // these do not exist in latin6 anymore: { 0x0653, 0 }, // 0x653 Mark Maddah above { 0x0654, 0 }, // 0x654 Mark Hamza above { 0x0655, 0 }, // 0x655 Mark Hamza below { 0x0656, 0 }, // 0x656 { 0x0657, 0 }, // 0x657 { 0x0658, 0 }, // 0x658 { 0x0659, 0 }, // 0x659 { 0x065a, 0 }, // 0x65a { 0x065b, 0 }, // 0x65b { 0x065c, 0 }, // 0x65c { 0x065d, 0 }, // 0x65d { 0x065e, 0 }, // 0x65e { 0x065f, 0 }, // 0x65f { 0x0660, 0 }, // 0x660 Arabic 0 { 0x0661, 0 }, // 0x661 Arabic 1 { 0x0662, 0 }, // 0x662 Arabic 2 { 0x0663, 0 }, // 0x663 Arabic 3 { 0x0664, 0 }, // 0x664 Arabic 4 { 0x0665, 0 }, // 0x665 Arabic 5 { 0x0666, 0 }, // 0x666 Arabic 6 { 0x0667, 0 }, // 0x667 Arabic 7 { 0x0668, 0 }, // 0x668 Arabic 8 { 0x0669, 0 }, // 0x669 Arabic 9 { 0x066a, 0 }, // 0x66a Arabic % sign { 0x066b, 0 }, // 0x66b Arabic decimal separator { 0x066c, 0 }, // 0x66c Arabic thousands separator { 0x066d, 0 }, // 0x66d Arabic five pointed star { 0x066e, 0 }, // 0x66e { 0x066f, 0 }, // 0x66f // ### some glyphs do not have tqshaped mappings in the presentation forms A. // these have the shaping set to 0 for the moment. Will have to find out better mappings for them. { 0x0670, 0 }, // 0x670 { 0xfb50, 1 }, // 0x671 R Alef Wasla { 0x0672, 0 }, // 0x672 R Alef with wavy Hamza above { 0x0673, 0 }, // 0x673 R Alef with wavy Hamza below { 0x0674, 0 }, // 0x674 U High Hamza { 0x0675, 0 }, // 0x675 R High Hamza Alef { 0x0676, 0 }, // 0x676 R High Hamza Wav { 0xfbdd, 0 }, // 0x677 R U with hamza above // ### only isolated form found... { 0x0678, 0 }, // 0x678 D High hamza yeh { 0xfb66, 3 }, // 0x679 D ttheh { 0xfb5e, 3 }, // 0x67a D theheh { 0xfb52, 3 }, // 0x67b D beeh { 0x067c, 0 }, // 0x67cD teh with ring { 0x067d, 0 }, // 0x67d D teh with three dots above downwards { 0xfb56, 3 }, // 0x67e D peh { 0xfb62, 3 }, // 0x67f D teheh { 0xfb5a, 3 }, // 0x680 D beheh { 0x0681, 0 }, // 0x681 D hah with hamza above { 0x0682, 0 }, // 0x682 D hah with two dots vertical above { 0xfb76, 3 }, // 0x683 D nyeh { 0xfb72, 3 }, // 0x684 D dyeh { 0x0685, 0 }, // 0x685 D hah with three dots above { 0xfb7a, 3 }, // 0x686 D tcheh { 0xfb7e, 3 }, // 0x687 D tcheheh { 0xfb88, 1 }, // 0x688 R ddal { 0x0689, 0 }, // 0x689 R dal with ring { 0x068a, 0 }, // 0x68a R dal with dot { 0x068b, 0 }, // 0x68b R dal with dot below and small tah { 0xfb84, 1 }, // 0x68cR dahal { 0xfb82, 1 }, // 0x68d R ddahal { 0xfb86, 1 }, // 0x68e R dul { 0x068f, 0 }, // 0x68f R dal with three dots above downwards { 0x0690, 0 }, // 0x690 R dal with four dots above { 0xfb8c, 1 }, // 0x691 R rreh { 0x0692, 0 }, // 0x692 R reh with small v { 0x0693, 0 }, // 0x693 R reh with ring { 0x0694, 0 }, // 0x694 R reh with dot below { 0x0695, 0 }, // 0x695 R reh with small v below { 0x0696, 0 }, // 0x696 R reh with dot below and dot above { 0x0697, 0 }, // 0x697 R reh with two dots above { 0xfb8a, 1 }, // 0x698 R jeh { 0x0699, 0 }, // 0x699 R reh with four dots above { 0x069a, 0 }, // 0x69a D seen with dot below and dot above { 0x069b, 0 }, // 0x69b D seen with three dots below { 0x069c, 0 }, // 0x69cD seen with three dots below and three dots above { 0x069d, 0 }, // 0x69d D sad with two dots below { 0x069e, 0 }, // 0x69e D sad with three dots above { 0x069f, 0 }, // 0x69f D tah with three dots above { 0x06a0, 0 }, // 0x6a0 D ain with three dots above { 0x06a1, 0 }, // 0x6a1 D dotless feh { 0x06a2, 0 }, // 0x6a2 D feh with dot moved below { 0x06a3, 0 }, // 0x6a3 D feh with dot below { 0xfb6a, 3 }, // 0x6a4 D veh { 0x06a5, 0 }, // 0x6a5 D feh with three dots below { 0xfb6e, 3 }, // 0x6a6 D peheh { 0x06a7, 0 }, // 0x6a7 D qaf with dot above { 0x06a8, 0 }, // 0x6a8 D qaf woith three dots above { 0xfb8e, 3 }, // 0x6a9 D keheh { 0x06aa, 0 }, // 0x6aa D swash kaf { 0x06ab, 0 }, // 0x6ab D kaf with ring { 0x06ac, 0 }, // 0x6acD kaf with dot above { 0xfbd3, 3 }, // 0x6ad D ng { 0x06ae, 0 }, // 0x6ae D kaf with three dots below { 0xfb92, 3 }, // 0x6af D gaf { 0x06b0, 0 }, // 0x6b0 D gaf with ring { 0xfb9a, 3 }, // 0x6b1 D ngoeh { 0x06b2, 0 }, // 0x6b2 D gaf with two dots below { 0xfb96, 3 }, // 0x6b3 D gueh { 0x06b4, 0 }, // 0x6b4 D gaf with three dots above { 0x06b5, 0 }, // 0x6b5 D lam with small v { 0x06b6, 0 }, // 0x6b6 D lam with dot above { 0x06b7, 0 }, // 0x6b7 D lam with three dots above { 0x06b8, 0 }, // 0x6b8 D lam with three dots below { 0x06b9, 0 }, // 0x6b9 D noon with dot below { 0xfb9e, 1 }, // 0x6ba R noon ghunna { 0xfba0, 3 }, // 0x6bb D rnoon { 0x06bc, 0 }, // 0x6bcD noon with ring { 0x06bd, 0 }, // 0x6bd D noon with three dots above { 0xfbaa, 3 }, // 0x6be D heh doachashmee { 0x06bf, 0 }, // 0x6bf D tcheh with dot above { 0xfba4, 1 }, // 0x6c0 R heh with yeh above = ligature hamza on hah (06d5 + 0654) { 0xfba6, 3 }, // 0x6c1 D heh goal { 0x06c2, 0 }, // 0x6c2 R heh goal with hamza above (06c1 + 0654) { 0x06c3, 0 }, // 0x6c3 R teh marbuta goal { 0x06c4, 0 }, // 0x6c4 R waw with ring { 0xfbe0, 1 }, // 0x6c5 R kirghiz oe { 0xfbd9, 1 }, // 0x6c6 R oe { 0xfbd7, 1 }, // 0x6c7 R u { 0xfbdb, 1 }, // 0x6c8 R yu { 0xfbe2, 1 }, // 0x6c9 R kirghiz yu { 0x06ca, 0 }, // 0x6ca R waw with teo dots above { 0xfbde, 1 }, // 0x6cb R ve { 0x06cc, 0 }, // 0x6cc D farsi yeh { 0x06cd, 0 }, // 0x6cd R yeh with tail { 0x06ce, 0 }, // 0x6ce D yeh with small v { 0x06cf, 0 }, // 0x6cf R waw with dot above { 0xfbe4, 3 }, // 0x6d0 D e { 0x06d1, 0 }, // 0x6d1 D yeh with three dots below { 0xfbae, 1 }, // 0x6d2 R yeh barree { 0xfbb0, 1 }, // 0x6d3 R yeh barree with hamza above { 0x06d4, 0 }, // 0x6d4 U full stop { 0x06d5, 0 }, // 0x6d5 D ae { 0x06d6, 0 }, // 0x6d6 koreanic annotaion signs { 0x06d7, 0 }, // 0x6d7 ... { 0x06d8, 0 }, // 0x6d8 { 0x06d9, 0 }, // 0x6d9 { 0x06da, 0 }, // 0x6da { 0x06db, 0 }, // 0x6db { 0x06dc, 0 }, // 0x6dc { 0x06dd, 0 }, // 0x6dd { 0x06de, 0 }, // 0x6de { 0x06df, 0 }, // 0x6df { 0x06e0, 0 }, // 0x6e0 { 0x06e1, 0 }, // 0x6e1 { 0x06e2, 0 }, // 0x6e2 { 0x06e3, 0 }, // 0x6e3 { 0x06e4, 0 }, // 0x6e4 { 0x06e5, 0 }, // 0x6e5 { 0x06e6, 0 }, // 0x6e6 { 0x06e7, 0 }, // 0x6e7 { 0x06e8, 0 }, // 0x6e8 { 0x06e9, 0 }, // 0x6e9 { 0x06ea, 0 }, // 0x6ea { 0x06eb, 0 }, // 0x6eb { 0x06ec, 0 }, // 0x6ec { 0x06ed, 0 }, // 0x6ed { 0x06ee, 0 }, // 0x6ee { 0x06ef, 0 }, // 0x6ef { 0x06f0, 0 }, // 0x6f0 Arabic indic digit 0 { 0x06f1, 0 }, // 0x6f1 { 0x06f2, 0 }, // 0x6f2 { 0x06f3, 0 }, // 0x6f3 { 0x06f4, 0 }, // 0x6f4 { 0x06f5, 0 }, // 0x6f5 { 0x06f6, 0 }, // 0x6f6 { 0x06f7, 0 }, // 0x6f7 { 0x06f8, 0 }, // 0x6f8 { 0x06f9, 0 }, // 0x6f9 Arabic indic digit 9 { 0x06fa, 0 }, // 0x6fa D Sheen with dot below { 0x06fb, 0 }, // 0x6fb D dad with dot below { 0x06fc, 0 }, // 0x6fc D ghain with dot below { 0x06fd, 0 }, // 0x6fd Sindhi ampersand { 0x06fe, 0 }, // 0x6fe sindhi postposition { 0x06ff, 0 }, // 0x6ff }; // this is a bit tricky. Alef always binds to the right, so the second parameter descibing the tqshape // of the lam can be either initial of medial. So initial maps to the isolated form of the ligature, // medial to the final form static const ushort arabicUnicodeLamAlefMapping[6][4] = { { 0xfffd, 0xfffd, 0xfef5, 0xfef6 }, // 0x622 R Alef with Madda above { 0xfffd, 0xfffd, 0xfef7, 0xfef8 }, // 0x623 R Alef with Hamza above { 0xfffd, 0xfffd, 0xfffd, 0xfffd }, // 0x624 R Waw with Hamza above { 0xfffd, 0xfffd, 0xfef9, 0xfefa }, // 0x625 R Alef with Hamza below { 0xfffd, 0xfffd, 0xfffd, 0xfffd }, // 0x626 D Yeh with Hamza above { 0xfffd, 0xfffd, 0xfefb, 0xfefc } // 0x627 R Alef }; static inline int getShape( const TQChar * /* base */, uchar cell, int tqshape, const TQFontMetrics * /* fm */ ) { uint ch = arabicUnicodeMapping[cell][0] + tqshape; /* // we revert to the untqshaped glyph in case the tqshaped version doesn't exist if ( fm && !fm->inFont( ch ) ) { switch( tqshape ) { case KoComplexText::XIsolated: break; // try base form case KoComplexText::XFinal: ch -= 1; // try isolated form break; case KoComplexText::XInitial: ch += 1; // try medial form break; case KoComplexText::XMedial: ch -= 1; // try initial form break; } if ( !fm->inFont( ch ) ) ch = *base; } */ return ch; } TQString KoComplexText::tqshapedString(const TQString& uc, int from, int len, TQPainter::TextDirection dir, const TQFontMetrics *fm ) { if( len < 0 ) len = uc.length() - from; if( len == 0 ) { return TQString(); } // we have to ignore NSMs at the beginning and add at the end. int num = uc.length() - from - len; const TQChar *ch = uc.tqunicode() + from + len; while ( num > 0 && ch->combiningClass() != 0 ) { ch++; num--; len++; } ch = uc.tqunicode() + from; while ( len > 0 && ch->combiningClass() != 0 ) { ch++; len--; from++; } if ( len == 0 ) return TQString(); if( !tqshapeBuffer || len > tqshapeBufSize ) { if( tqshapeBuffer ) free( (void *) tqshapeBuffer ); tqshapeBuffer = (TQChar *) malloc( len*sizeof( TQChar ) ); // delete [] tqshapeBuffer; // tqshapeBuffer = new TQChar[ len + 1]; tqshapeBufSize = len; } int lenOut = 0; TQChar *data = tqshapeBuffer; if ( dir == TQPainter::RTL ) ch += len - 1; for ( int i = 0; i < len; i++ ) { uchar r = ch->row(); uchar c = ch->cell(); if ( r != 0x06 ) { if ( dir == TQPainter::RTL && ch->mirrored() ) *data = ch->mirroredChar(); else *data = *ch; data++; lenOut++; } else { int pos = i + from; if ( dir == TQPainter::RTL ) pos = from + len - 1 - i; int tqshape = glyphVariantLogical( uc, pos ); //kdDebug() << "mapping U+" << ch->tqunicode() << " to tqshape " << tqshape << " glyph=0x" << arabicUnicodeMapping[ch->cell()][tqshape] << endl; // take care of lam-alef ligatures (lam right of alef) ushort map; switch ( c ) { case 0x44: { // lam const TQChar *pch = nextChar( uc, pos ); if ( pch->row() == 0x06 ) { switch ( pch->cell() ) { case 0x22: case 0x23: case 0x25: case 0x27: //kdDebug() << " lam of lam-alef ligature" << endl; map = arabicUnicodeLamAlefMapping[pch->cell() - 0x22][tqshape]; goto next; default: break; } } break; } case 0x22: // alef with madda case 0x23: // alef with hamza above case 0x25: // alef with hamza below case 0x27: // alef if ( prevChar( uc, pos )->tqunicode() == 0x0644 ) { // have a lam alef ligature //kdDebug() << " alef of lam-alef ligature" << endl; goto skip; } default: break; } map = getShape( ch, c, tqshape, fm ); next: *data = map; data++; lenOut++; } skip: if ( dir == TQPainter::RTL ) ch--; else ch++; } if ( dir == TQPainter::Auto && !uc.simpleText() ) { return bidiReorderString( TQConstString( tqshapeBuffer, lenOut ).string() ); } if ( dir == TQPainter::RTL ) { // reverses the non spacing marks to be again after the base char TQChar *s = tqshapeBuffer; int i = 0; while ( i < lenOut ) { if ( s->combiningClass() != 0 ) { // non spacing marks int clen = 1; TQChar *ch = s; do { ch++; clen++; } while ( ch->combiningClass() != 0 ); int j = 0; TQChar *cp = s; while ( j < clen/2 ) { TQChar tmp = *cp; *cp = *ch; *ch = tmp; cp++; ch--; j++; } s += clen; i += clen; } else { s++; i++; } } } return TQConstString( tqshapeBuffer, lenOut ).string(); } TQChar KoComplexText::tqshapedCharacter( const TQString &str, int pos, const TQFontMetrics *fm ) { const TQChar *ch = str.tqunicode() + pos; if ( ch->row() != 0x06 ) return *ch; else { int tqshape = glyphVariantLogical( str, pos ); //kdDebug() << "mapping U+" << ch->tqunicode() << " to tqshape " << tqshape << " glyph=0x" << arabicUnicodeMapping[ch->cell()][tqshape] << endl; // lam aleph ligatures switch ( ch->cell() ) { case 0x44: { // lam const TQChar *nch = nextChar( str, pos ); if ( nch->row() == 0x06 ) { switch ( nch->cell() ) { case 0x22: case 0x23: case 0x25: case 0x27: return TQChar(arabicUnicodeLamAlefMapping[nch->cell() - 0x22][tqshape]); default: break; } } break; } case 0x22: // alef with madda case 0x23: // alef with hamza above case 0x25: // alef with hamza below case 0x27: // alef if ( prevChar( str, pos )->tqunicode() == 0x0644 ) // have a lam alef ligature return TQChar(0); default: break; } return TQChar( getShape( ch, ch->cell(), tqshape, fm ) ); } } // Avoid using TQFontPrivate, to which we don't have access. We don't use positionMarks() anyway #if 0 TQPointArray KoComplexText::positionMarks( TQFontPrivate *f, const TQString &str, int pos, TQRect *boundingRect ) { int len = str.length(); int nmarks = 0; while ( pos + nmarks < len && str[pos+nmarks +1].combiningClass() > 0 ) nmarks++; if ( !nmarks ) return TQPointArray(); TQChar baseChar = KoComplexText::tqshapedCharacter( str, pos ); TQRect baseRect = f->boundingRect( baseChar ); int baseOffset = f->textWidth( str, pos, 1 ); //kdDebug() << "base char: bounding rect at " << baseRect.x() << "/" << baseRect.y() << " (" << baseRect.width() << "/" << baseRect.height() << ")" << endl; int offset = f->actual.pixelSize / 10 + 1; //kdDebug() << "offset = " << offset << endl; TQPointArray pa( nmarks ); int i; unsigned char lastCmb = 0; TQRect attachmentRect; if ( boundingRect ) *boundingRect = baseRect; for( i = 0; i < nmarks; i++ ) { TQChar mark = str[pos+i+1]; unsigned char cmb = mark.combiningClass(); if ( cmb < 200 ) { // fixed position classes. We approximate by mapping to one of the others. // currently I added only the ones for arabic, hebrew and thai. // ### add a bit more offset to arabic, a bit hacky if ( cmb >= 27 && cmb <= 36 ) offset +=1; // below if ( (cmb >= 10 && cmb <= 18) || cmb == 20 || cmb == 22 || cmb == 29 || cmb == 32 ) cmb = TQChar::Combining_Below; // above else if ( cmb == 23 || cmb == 27 || cmb == 28 || cmb == 30 || cmb == 31 || (cmb >= 33 && cmb <= 36 ) ) cmb = TQChar::Combining_Above; //below-right else if ( cmb == 103 ) cmb = TQChar::Combining_BelowRight; // above-right else if ( cmb == 24 || cmb == 107 ) cmb = TQChar::Combining_AboveRight; else if ( cmb == 25 ) cmb = TQChar::Combining_AboveLeft; // fixed: // 19 21 } // combining marks of different class don't interact. Reset the rectangle. if ( cmb != lastCmb ) { //kdDebug() << "resetting rect" << endl; attachmentRect = baseRect; } TQPoint p; TQRect markRect = f->boundingRect( mark ); switch( cmb ) { case TQChar::Combining_DoubleBelow: // ### wrong in rtl context! case TQChar::Combining_BelowLeft: p += TQPoint( 0, offset ); case TQChar::Combining_BelowLeftAttached: p += attachmentRect.bottomLeft() - markRect.topLeft(); break; case TQChar::Combining_Below: p += TQPoint( 0, offset ); case TQChar::Combining_BelowAttached: p += attachmentRect.bottomLeft() - markRect.topLeft(); p += TQPoint( (attachmentRect.width() - markRect.width())/2 , 0 ); break; case TQChar::Combining_BelowRight: p += TQPoint( 0, offset ); case TQChar::Combining_BelowRightAttached: p += attachmentRect.bottomRight() - markRect.topRight(); break; case TQChar::Combining_Left: p += TQPoint( -offset, 0 ); case TQChar::Combining_LeftAttached: break; case TQChar::Combining_Right: p += TQPoint( offset, 0 ); case TQChar::Combining_RightAttached: break; case TQChar::Combining_DoubleAbove: // ### wrong in RTL context! case TQChar::Combining_AboveLeft: p += TQPoint( 0, -offset ); case TQChar::Combining_AboveLeftAttached: p += attachmentRect.topLeft() - markRect.bottomLeft(); break; case TQChar::Combining_Above: p += TQPoint( 0, -offset ); case TQChar::Combining_AboveAttached: p += attachmentRect.topLeft() - markRect.bottomLeft(); p += TQPoint( (attachmentRect.width() - markRect.width())/2 , 0 ); break; case TQChar::Combining_AboveRight: p += TQPoint( 0, -offset ); case TQChar::Combining_AboveRightAttached: p += attachmentRect.topRight() - markRect.bottomRight(); break; case TQChar::Combining_IotaSubscript: default: break; } //kdDebug() << "char=" << mark.tqunicode() << " combiningClass = " << cmb << " offset=" << p.x() << "/" << p.y() << endl; markRect.moveBy( p.x(), p.y() ); p += TQPoint( -baseOffset, 0 ); attachmentRect |= markRect; if ( boundingRect ) *boundingRect |= markRect; lastCmb = cmb; pa.setPoint( i, p ); } return pa; } #endif //#define BIDI_DEBUG #ifdef BIDI_DEBUG #include #endif static TQChar::Direction basicDirection(const TQString &str, int start = 0) { int len = str.length(); int pos = start > len ? len -1 : start; const TQChar *uc = str.tqunicode() + pos; while( pos < len ) { switch( uc->direction() ) { case TQChar::DirL: case TQChar::DirLRO: case TQChar::DirLRE: return TQChar::DirL; case TQChar::DirR: case TQChar::DirAL: case TQChar::DirRLO: case TQChar::DirRLE: return TQChar::DirR; default: break; } ++pos; ++uc; } if ( start != 0 ) return basicDirection( str ); return TQChar::DirL; } // transforms one line of the paragraph to visual order // the caller is responisble to delete the returned list of KoTextRuns. TQPtrList *KoComplexText::bidiReorderLine( KoBidiControl *control, const TQString &text, int start, int len, TQChar::Direction basicDir ) { int last = start + len - 1; //printf("doing BiDi reordering from %d to %d!\n", start, last); TQPtrList *runs = new TQPtrList; runs->setAutoDelete(TRUE); KoBidiContext *context = control->context; if ( !context ) { // first line //if( start != 0 ) // kdDebug() << "bidiReorderLine::internal error" << endl; if( basicDir == TQChar::DirR || (basicDir == TQChar::DirON && text.isRightToLeft() ) ) { context = new KoBidiContext( 1, TQChar::DirR ); control->status.last = TQChar::DirR; } else { context = new KoBidiContext( 0, TQChar::DirL ); control->status.last = TQChar::DirL; } } KoBidiStatus status = control->status; TQChar::Direction dir = TQChar::DirON; int sor = start; int eor = start; int current = start; while(current <= last) { TQChar::Direction dirCurrent; if(current == (int)text.length()) { KoBidiContext *c = context; while ( c->parent ) c = c->parent; dirCurrent = c->dir; } else if ( current == last ) { dirCurrent = ( basicDir != TQChar::DirON ? basicDir : basicDirection( text, current ) ); } else dirCurrent = text.at(current).direction(); #ifdef BIDI_DEBUG cout << "directions: dir=" << dir << " current=" << dirCurrent << " last=" << status.last << " eor=" << status.eor << " lastStrong=" << status.lastStrong << " embedding=" << context->dir << " level =" << (int)context->level << endl; #endif switch(dirCurrent) { // embedding and overrides (X1-X9 in the BiDi specs) case TQChar::DirRLE: { uchar level = context->level; if(level%2) // we have an odd level level += 2; else level++; if(level < 61) { runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; context = new KoBidiContext(level, TQChar::DirR, context); status.last = TQChar::DirR; status.lastStrong = TQChar::DirR; } break; } case TQChar::DirLRE: { uchar level = context->level; if(level%2) // we have an odd level level++; else level += 2; if(level < 61) { runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; context = new KoBidiContext(level, TQChar::DirL, context); status.last = TQChar::DirL; status.lastStrong = TQChar::DirL; } break; } case TQChar::DirRLO: { uchar level = context->level; if(level%2) // we have an odd level level += 2; else level++; if(level < 61) { runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; context = new KoBidiContext(level, TQChar::DirR, context, TRUE); dir = TQChar::DirR; status.last = TQChar::DirR; status.lastStrong = TQChar::DirR; } break; } case TQChar::DirLRO: { uchar level = context->level; if(level%2) // we have an odd level level++; else level += 2; if(level < 61) { runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; context = new KoBidiContext(level, TQChar::DirL, context, TRUE); dir = TQChar::DirL; status.last = TQChar::DirL; status.lastStrong = TQChar::DirL; } break; } case TQChar::DirPDF: { KoBidiContext *c = context->parent; if(c) { runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; status.last = context->dir; if( context->deref() ) delete context; context = c; if(context->override) dir = context->dir; else dir = TQChar::DirON; status.lastStrong = context->dir; } break; } // strong types case TQChar::DirL: if(dir == TQChar::DirON) dir = TQChar::DirL; switch(status.last) { case TQChar::DirL: eor = current; status.eor = TQChar::DirL; break; case TQChar::DirR: case TQChar::DirAL: case TQChar::DirEN: case TQChar::DirAN: runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; break; case TQChar::DirES: case TQChar::DirET: case TQChar::DirCS: case TQChar::DirBN: case TQChar::DirB: case TQChar::DirS: case TQChar::DirWS: case TQChar::DirON: if(dir != TQChar::DirL) { //last stuff takes embedding dir if( context->dir == TQChar::DirR ) { if(status.eor != TQChar::DirR) { // AN or EN runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; dir = TQChar::DirR; } else eor = current - 1; runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; } else { if(status.eor != TQChar::DirL) { runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; dir = TQChar::DirL; } else { eor = current; status.eor = TQChar::DirL; break; } } } else { eor = current; status.eor = TQChar::DirL; } default: break; } status.lastStrong = TQChar::DirL; break; case TQChar::DirAL: case TQChar::DirR: if(dir == TQChar::DirON) dir = TQChar::DirR; switch(status.last) { case TQChar::DirR: case TQChar::DirAL: eor = current; status.eor = TQChar::DirR; break; case TQChar::DirL: case TQChar::DirEN: case TQChar::DirAN: runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; break; case TQChar::DirES: case TQChar::DirET: case TQChar::DirCS: case TQChar::DirBN: case TQChar::DirB: case TQChar::DirS: case TQChar::DirWS: case TQChar::DirON: if( status.eor != TQChar::DirR && status.eor != TQChar::DirAL ) { //last stuff takes embedding dir if(context->dir == TQChar::DirR || status.lastStrong == TQChar::DirR) { runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; dir = TQChar::DirR; eor = current; } else { eor = current - 1; runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; dir = TQChar::DirR; } } else { eor = current; status.eor = TQChar::DirR; } default: break; } status.lastStrong = dirCurrent; break; // weak types: case TQChar::DirNSM: // ### if @sor, set dir to dirSor break; case TQChar::DirEN: if(status.lastStrong != TQChar::DirAL) { // if last strong was AL change EN to AL if(dir == TQChar::DirON) { if(status.lastStrong == TQChar::DirL) dir = TQChar::DirL; else dir = TQChar::DirAN; } switch(status.last) { case TQChar::DirET: if ( status.lastStrong == TQChar::DirR || status.lastStrong == TQChar::DirAL ) { runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; status.eor = TQChar::DirON; dir = TQChar::DirAN; } // fall through case TQChar::DirEN: case TQChar::DirL: eor = current; status.eor = dirCurrent; break; case TQChar::DirR: case TQChar::DirAL: case TQChar::DirAN: runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; status.eor = TQChar::DirEN; dir = TQChar::DirAN; break; case TQChar::DirES: case TQChar::DirCS: if(status.eor == TQChar::DirEN || dir == TQChar::DirAN) { eor = current; break; } case TQChar::DirBN: case TQChar::DirB: case TQChar::DirS: case TQChar::DirWS: case TQChar::DirON: if(status.eor == TQChar::DirR) { // neutrals go to R eor = current - 1; runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirEN; dir = TQChar::DirAN; } else if( status.eor == TQChar::DirL || (status.eor == TQChar::DirEN && status.lastStrong == TQChar::DirL)) { eor = current; status.eor = dirCurrent; } else { // numbers on both sides, neutrals get right to left direction if(dir != TQChar::DirL) { runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; eor = current - 1; dir = TQChar::DirR; runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; dir = TQChar::DirAN; } else { eor = current; status.eor = dirCurrent; } } default: break; } break; } case TQChar::DirAN: dirCurrent = TQChar::DirAN; if(dir == TQChar::DirON) dir = TQChar::DirAN; switch(status.last) { case TQChar::DirL: case TQChar::DirAN: eor = current; status.eor = TQChar::DirAN; break; case TQChar::DirR: case TQChar::DirAL: case TQChar::DirEN: runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; break; case TQChar::DirCS: if(status.eor == TQChar::DirAN) { eor = current; status.eor = TQChar::DirR; break; } case TQChar::DirES: case TQChar::DirET: case TQChar::DirBN: case TQChar::DirB: case TQChar::DirS: case TQChar::DirWS: case TQChar::DirON: if(status.eor == TQChar::DirR) { // neutrals go to R eor = current - 1; runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; dir = TQChar::DirAN; } else if( status.eor == TQChar::DirL || (status.eor == TQChar::DirEN && status.lastStrong == TQChar::DirL)) { eor = current; status.eor = dirCurrent; } else { // numbers on both sides, neutrals get right to left direction if(dir != TQChar::DirL) { runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; eor = current - 1; dir = TQChar::DirR; runs->append( new KoTextRun(sor, eor, context, dir) ); ++eor; sor = eor; dir = TQChar::DirON; status.eor = TQChar::DirON; dir = TQChar::DirAN; } else { eor = current; status.eor = dirCurrent; } } default: break; } break; case TQChar::DirES: case TQChar::DirCS: break; case TQChar::DirET: if(status.last == TQChar::DirEN) { dirCurrent = TQChar::DirEN; eor = current; status.eor = dirCurrent; break; } break; // boundary neutrals should be ignored case TQChar::DirBN: break; // neutrals case TQChar::DirB: // ### what do we do with newline and paragraph separators that come to here? break; case TQChar::DirS: // ### implement rule L1 break; case TQChar::DirWS: case TQChar::DirON: break; default: break; } //cout << " after: dir=" << // dir << " current=" << dirCurrent << " last=" << status.last << " eor=" << status.eor << " lastStrong=" << status.lastStrong << " embedding=" << context->dir << endl; if(current >= (int)text.length()) break; // set status.last as needed. switch(dirCurrent) { case TQChar::DirET: case TQChar::DirES: case TQChar::DirCS: case TQChar::DirS: case TQChar::DirWS: case TQChar::DirON: switch(status.last) { case TQChar::DirL: case TQChar::DirR: case TQChar::DirAL: case TQChar::DirEN: case TQChar::DirAN: status.last = dirCurrent; break; default: status.last = TQChar::DirON; } break; case TQChar::DirNSM: case TQChar::DirBN: // ignore these break; default: status.last = dirCurrent; } ++current; } #ifdef BIDI_DEBUG cout << "reached end of line current=" << current << ", eor=" << eor << endl; #endif eor = current - 1; // remove dummy char if ( sor <= eor ) runs->append( new KoTextRun(sor, eor, context, dir) ); // reorder line according to run structure... // first find highest and lowest levels uchar levelLow = 128; uchar levelHigh = 0; KoTextRun *r = runs->first(); while ( r ) { //printf("level = %d\n", r->level); if ( r->level > levelHigh ) levelHigh = r->level; if ( r->level < levelLow ) levelLow = r->level; r = runs->next(); } // 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++; #ifdef BIDI_DEBUG cout << "reorderLine: lineLow = " << (uint)levelLow << ", lineHigh = " << (uint)levelHigh << endl; cout << "logical order is:" << endl; TQPtrListIterator it2(*runs); KoTextRun *r2; for ( ; (r2 = it2.current()); ++it2 ) cout << " " << r2 << " start=" << r2->start << " stop=" << r2->stop << " level=" << (uint)r2->level << endl; #endif int count = runs->count() - 1; while(levelHigh >= levelLow) { int i = 0; while ( i < count ) { while(i < count && runs->at(i)->level < levelHigh) i++; int start = i; while(i <= count && runs->at(i)->level >= levelHigh) i++; int end = i-1; if(start != end) { //cout << "reversing from " << start << " to " << end << endl; for(int j = 0; j < (end-start+1)/2; j++) { KoTextRun *first = runs->take(start+j); KoTextRun *last = runs->take(end-j-1); runs->insert(start+j, last); runs->insert(end-j, first); } } i++; if(i >= count) break; } levelHigh--; } #ifdef BIDI_DEBUG cout << "visual order is:" << endl; TQPtrListIterator it3(*runs); KoTextRun *r3; for ( ; (r3 = it3.current()); ++it3 ) { cout << " " << r3 << endl; } #endif control->setContext( context ); control->status = status; return runs; } TQString KoComplexText::bidiReorderString( const TQString &str, TQChar::Direction /*basicDir*/ ) { // ### fix basic direction KoBidiControl control; int lineStart = 0; int lineEnd = 0; int len = str.length(); TQString visual; visual.setUnicode( 0, len ); TQChar *vch = (TQChar *)visual.tqunicode(); const TQChar *ch = str.tqunicode(); while( lineStart < len ) { lineEnd = lineStart; while( *ch != '\n' && lineEnd < len ) { ch++; lineEnd++; } lineEnd++; TQPtrList *runs = bidiReorderLine( &control, str, lineStart, lineEnd - lineStart ); // reorder the content of the line, and output to visual KoTextRun *r = runs->first(); while ( r ) { if(r->level %2) { // odd level, need to reverse the string int pos = r->stop; while(pos >= r->start) { *vch = str[pos]; if ( vch->mirrored() ) *vch = vch->mirroredChar(); vch++; pos--; } } else { int pos = r->start; while(pos <= r->stop) { *vch = str[pos]; vch++; pos++; } } r = runs->next(); } if ( *ch == '\n' ) { *vch = *ch; vch++; ch++; lineEnd++; } lineStart = lineEnd; } return visual; } KoTextRun::KoTextRun(int _start, int _stop, KoBidiContext *context, TQChar::Direction dir) { start = _start; stop = _stop; if(dir == TQChar::DirON) dir = context->dir; level = context->level; // add level of run (cases I1 & I2) if( level % 2 ) { if(dir == TQChar::DirL || dir == TQChar::DirAN) level++; } else { if( dir == TQChar::DirR ) level++; else if( dir == TQChar::DirAN ) level += 2; } #ifdef BIDI_DEBUG printf("new run: dir=%d from %d, to %d level = %d\n", dir, _start, _stop, level); #endif } #endif //TQT_NO_COMPLEXTEXT