2014-05-30 12:53:19 +04:00
/*
This file is part of Telegram Desktop ,
2014-12-01 13:47:38 +03:00
the official desktop version of Telegram messaging app , see https : //telegram.org
2014-05-30 12:53:19 +04:00
Telegram Desktop is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
It is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
2015-10-03 16:16:42 +03:00
In addition , as a special exception , the copyright holders give permission
to link the code of portions of this program with the OpenSSL library .
2014-05-30 12:53:19 +04:00
Full license : https : //github.com/telegramdesktop/tdesktop/blob/master/LICENSE
2017-01-11 22:31:31 +04:00
Copyright ( c ) 2014 - 2017 John Preston , https : //desktop.telegram.org
2014-05-30 12:53:19 +04:00
*/
2016-04-14 14:00:23 +03:00
# include "ui/text/text.h"
2014-05-30 12:53:19 +04:00
2016-04-13 00:31:28 +03:00
# include <private/qharfbuzz_p.h>
2014-05-30 12:53:19 +04:00
2016-04-13 00:31:28 +03:00
# include "core/click_handler_types.h"
2016-04-14 14:00:23 +03:00
# include "ui/text/text_block.h"
2017-04-13 11:27:10 +03:00
# include "lang/lang_keys.h"
2017-03-04 13:23:56 +03:00
# include "platform/platform_specific.h"
2017-04-06 17:38:10 +03:00
# include "boxes/confirm_box.h"
2016-04-13 00:31:28 +03:00
# include "mainwindow.h"
2014-05-30 12:53:19 +04:00
namespace {
2016-12-23 16:21:01 +03:00
inline int32 countBlockHeight ( const ITextBlock * b , const style : : TextStyle * st ) {
return ( b - > type ( ) = = TextBlockTSkip ) ? static_cast < const SkipBlock * > ( b ) - > height ( ) : ( st - > lineHeight > st - > font - > height ) ? st - > lineHeight : st - > font - > height ;
2015-04-07 01:15:29 +03:00
}
2016-04-14 14:00:23 +03:00
} // namespace
2015-06-10 18:54:24 +03:00
2017-12-01 20:55:16 +04:00
bool chIsBad ( QChar ch ) {
# ifdef OS_MAC_OLD
if ( cIsSnowLeopard ( ) & & ( ch = = 8207 | | ch = = 8206 ) ) {
return true ;
}
# endif // OS_MAC_OLD
return ( ch = = 0 ) | | ( ch > = 8232 & & ch < 8237 ) | | ( ch > = 65024 & & ch < 65040 & & ch ! = 65039 ) | | ( ch > = 127 & & ch < 160 & & ch ! = 156 ) | | ( cPlatform ( ) = = dbipMac & & ch > = 0x0B00 & & ch < = 0x0B7F & & chIsDiac ( ch ) & & cIsElCapitan ( ) ) ; // tmp hack see https://bugreports.qt.io/browse/QTBUG-48910
}
2014-05-30 12:53:19 +04:00
QString textcmdSkipBlock ( ushort w , ushort h ) {
static QString cmd ( 5 , TextCommand ) ;
cmd [ 1 ] = QChar ( TextCommandSkipBlock ) ;
cmd [ 2 ] = QChar ( w ) ;
cmd [ 3 ] = QChar ( h ) ;
return cmd ;
}
QString textcmdStartLink ( ushort lnkIndex ) {
static QString cmd ( 4 , TextCommand ) ;
cmd [ 1 ] = QChar ( TextCommandLinkIndex ) ;
cmd [ 2 ] = QChar ( lnkIndex ) ;
return cmd ;
}
QString textcmdStartLink ( const QString & url ) {
if ( url . size ( ) > = 4096 ) return QString ( ) ;
QString result ;
result . reserve ( url . size ( ) + 4 ) ;
return result . append ( TextCommand ) . append ( QChar ( TextCommandLinkText ) ) . append ( QChar ( url . size ( ) ) ) . append ( url ) . append ( TextCommand ) ;
}
QString textcmdStopLink ( ) {
return textcmdStartLink ( 0 ) ;
}
QString textcmdLink ( ushort lnkIndex , const QString & text ) {
QString result ;
result . reserve ( 4 + text . size ( ) + 4 ) ;
return result . append ( textcmdStartLink ( lnkIndex ) ) . append ( text ) . append ( textcmdStopLink ( ) ) ;
}
QString textcmdLink ( const QString & url , const QString & text ) {
QString result ;
result . reserve ( 4 + url . size ( ) + text . size ( ) + 4 ) ;
return result . append ( textcmdStartLink ( url ) ) . append ( text ) . append ( textcmdStopLink ( ) ) ;
}
2016-03-05 00:04:15 +02:00
QString textcmdStartSemibold ( ) {
QString result ;
result . reserve ( 3 ) ;
return result . append ( TextCommand ) . append ( QChar ( TextCommandSemibold ) ) . append ( TextCommand ) ;
}
QString textcmdStopSemibold ( ) {
QString result ;
result . reserve ( 3 ) ;
return result . append ( TextCommand ) . append ( QChar ( TextCommandNoSemibold ) ) . append ( TextCommand ) ;
}
2014-12-18 21:40:49 +03:00
const QChar * textSkipCommand ( const QChar * from , const QChar * end , bool canLink ) {
2014-10-07 21:57:57 +04:00
const QChar * result = from + 1 ;
if ( * from ! = TextCommand | | result > = end ) return from ;
ushort cmd = result - > unicode ( ) ;
+ + result ;
if ( result > = end ) return from ;
switch ( cmd ) {
case TextCommandBold :
case TextCommandNoBold :
2016-03-05 00:04:15 +02:00
case TextCommandSemibold :
case TextCommandNoSemibold :
2014-10-07 21:57:57 +04:00
case TextCommandItalic :
case TextCommandNoItalic :
case TextCommandUnderline :
case TextCommandNoUnderline :
break ;
case TextCommandLinkIndex :
if ( result - > unicode ( ) > 0x7FFF ) return from ;
+ + result ;
break ;
case TextCommandLinkText : {
ushort len = result - > unicode ( ) ;
if ( len > = 4096 | | ! canLink ) return from ;
result + = len + 1 ;
} break ;
case TextCommandSkipBlock :
result + = 2 ;
break ;
2014-12-18 21:40:49 +03:00
case TextCommandLangTag :
result + = 1 ;
break ;
2014-10-07 21:57:57 +04:00
}
return ( result < end & & * result = = TextCommand ) ? ( result + 1 ) : from ;
}
class TextParser {
2014-05-30 12:53:19 +04:00
public :
static Qt : : LayoutDirection stringDirection ( const QString & str , int32 from , int32 to ) {
const ushort * p = reinterpret_cast < const ushort * > ( str . unicode ( ) ) + from ;
const ushort * end = p + ( to - from ) ;
while ( p < end ) {
uint ucs4 = * p ;
if ( QChar : : isHighSurrogate ( ucs4 ) & & p < end - 1 ) {
ushort low = p [ 1 ] ;
if ( QChar : : isLowSurrogate ( low ) ) {
ucs4 = QChar : : surrogateToUcs4 ( ucs4 , low ) ;
+ + p ;
}
}
switch ( QChar : : direction ( ucs4 ) ) {
case QChar : : DirL :
return Qt : : LeftToRight ;
case QChar : : DirR :
case QChar : : DirAL :
return Qt : : RightToLeft ;
default :
break ;
}
+ + p ;
}
return Qt : : LayoutDirectionAuto ;
}
void blockCreated ( ) {
sumWidth + = _t - > _blocks . back ( ) - > f_width ( ) ;
2014-06-14 23:32:11 +04:00
if ( sumWidth . floor ( ) . toInt ( ) > stopAfterWidth ) {
2014-05-30 12:53:19 +04:00
sumFinished = true ;
}
}
void createBlock ( int32 skipBack = 0 ) {
if ( lnkIndex < 0x8000 & & lnkIndex > maxLnkIndex ) maxLnkIndex = lnkIndex ;
int32 len = int32 ( _t - > _text . size ( ) ) + skipBack - blockStart ;
if ( len > 0 ) {
2015-10-23 18:06:56 +02:00
bool newline = ! emoji & & ( len = = 1 & & _t - > _text . at ( blockStart ) = = QChar : : LineFeed ) ;
if ( newlineAwaited ) {
newlineAwaited = false ;
if ( ! newline ) {
_t - > _text . insert ( blockStart , QChar : : LineFeed ) ;
createBlock ( skipBack - len ) ;
}
}
lastSkipped = false ;
2014-05-30 12:53:19 +04:00
if ( emoji ) {
2017-07-06 18:58:05 +03:00
_t - > _blocks . push_back ( std : : make_unique < EmojiBlock > ( _t - > _st - > font , _t - > _text , blockStart , len , flags , lnkIndex , emoji ) ) ;
2014-05-30 12:53:19 +04:00
emoji = 0 ;
lastSkipped = true ;
2015-10-23 18:06:56 +02:00
} else if ( newline ) {
2017-07-06 18:58:05 +03:00
_t - > _blocks . push_back ( std : : make_unique < NewlineBlock > ( _t - > _st - > font , _t - > _text , blockStart , len , flags , lnkIndex ) ) ;
2014-05-30 12:53:19 +04:00
} else {
2017-07-06 18:58:05 +03:00
_t - > _blocks . push_back ( std : : make_unique < TextBlock > ( _t - > _st - > font , _t - > _text , _t - > _minResizeWidth , blockStart , len , flags , lnkIndex ) ) ;
2014-05-30 12:53:19 +04:00
}
blockStart + = len ;
blockCreated ( ) ;
}
}
void createSkipBlock ( int32 w , int32 h ) {
createBlock ( ) ;
_t - > _text . push_back ( ' _ ' ) ;
2017-07-06 18:58:05 +03:00
_t - > _blocks . push_back ( std : : make_unique < SkipBlock > ( _t - > _st - > font , _t - > _text , blockStart + + , w , h , lnkIndex ) ) ;
2014-05-30 12:53:19 +04:00
blockCreated ( ) ;
}
2015-10-23 18:06:56 +02:00
void createNewlineBlock ( ) {
createBlock ( ) ;
_t - > _text . push_back ( QChar : : LineFeed ) ;
createBlock ( ) ;
}
2015-11-23 18:34:38 +03:00
bool checkCommand ( ) {
bool result = false ;
for ( QChar c = ( ( ptr < end ) ? * ptr : 0 ) ; c = = TextCommand ; c = ( ( ptr < end ) ? * ptr : 0 ) ) {
2015-10-23 18:06:56 +02:00
if ( ! readCommand ( ) ) {
break ;
}
2015-11-23 18:34:38 +03:00
result = true ;
2014-05-30 12:53:19 +04:00
}
2015-11-23 18:34:38 +03:00
return result ;
2015-10-23 18:06:56 +02:00
}
2014-05-30 12:53:19 +04:00
2016-05-04 19:46:24 +03:00
// Returns true if at least one entity was parsed in the current position.
bool checkEntities ( ) {
2015-10-23 18:06:56 +02:00
while ( ! removeFlags . isEmpty ( ) & & ( ptr > = removeFlags . firstKey ( ) | | ptr > = end ) ) {
const QList < int32 > & removing ( removeFlags . first ( ) ) ;
for ( int32 i = removing . size ( ) ; i > 0 ; ) {
int32 flag = removing . at ( - - i ) ;
if ( flags & flag ) {
createBlock ( ) ;
flags & = ~ flag ;
if ( flag = = TextBlockFPre ) {
newlineAwaited = true ;
}
}
}
removeFlags . erase ( removeFlags . begin ( ) ) ;
}
2016-04-29 15:00:48 +03:00
while ( waitingEntity ! = entitiesEnd & & start + waitingEntity - > offset ( ) + waitingEntity - > length ( ) < = ptr ) {
2015-10-25 18:08:45 +01:00
+ + waitingEntity ;
}
2016-04-29 15:00:48 +03:00
if ( waitingEntity = = entitiesEnd | | ptr < start + waitingEntity - > offset ( ) ) {
2016-05-04 19:46:24 +03:00
return false ;
2015-10-23 18:06:56 +02:00
}
2014-05-30 12:53:19 +04:00
2015-10-23 18:06:56 +02:00
int32 startFlags = 0 ;
2016-04-30 20:04:14 +03:00
QString linkData , linkText ;
auto type = waitingEntity - > type ( ) , linkType = EntityInTextInvalid ;
LinkDisplayStatus linkDisplayStatus = LinkDisplayedFull ;
if ( type = = EntityInTextBold ) {
2015-10-23 18:06:56 +02:00
startFlags = TextBlockFSemibold ;
2016-04-29 15:00:48 +03:00
} else if ( type = = EntityInTextItalic ) {
2015-10-23 18:06:56 +02:00
startFlags = TextBlockFItalic ;
2016-04-29 15:00:48 +03:00
} else if ( type = = EntityInTextCode ) {
2015-10-23 18:06:56 +02:00
startFlags = TextBlockFCode ;
2016-04-29 15:00:48 +03:00
} else if ( type = = EntityInTextPre ) {
2015-10-23 18:06:56 +02:00
startFlags = TextBlockFPre ;
createBlock ( ) ;
2017-07-06 18:58:05 +03:00
if ( ! _t - > _blocks . empty ( ) & & _t - > _blocks . back ( ) - > type ( ) ! = TextBlockTNewline ) {
2015-10-23 18:06:56 +02:00
createNewlineBlock ( ) ;
}
2016-04-30 20:04:14 +03:00
} else if ( type = = EntityInTextUrl
| | type = = EntityInTextEmail
| | type = = EntityInTextMention
| | type = = EntityInTextHashtag
| | type = = EntityInTextBotCommand ) {
linkType = type ;
linkData = QString ( start + waitingEntity - > offset ( ) , waitingEntity - > length ( ) ) ;
if ( linkType = = EntityInTextUrl ) {
computeLinkText ( linkData , & linkText , & linkDisplayStatus ) ;
} else {
linkText = linkData ;
}
} else if ( type = = EntityInTextCustomUrl | | type = = EntityInTextMentionName ) {
linkType = type ;
linkData = waitingEntity - > data ( ) ;
linkText = QString ( start + waitingEntity - > offset ( ) , waitingEntity - > length ( ) ) ;
2015-09-10 14:20:28 +03:00
}
2014-05-30 12:53:19 +04:00
2016-04-30 20:04:14 +03:00
if ( linkType ! = EntityInTextInvalid ) {
2015-10-23 18:06:56 +02:00
createBlock ( ) ;
2014-05-30 12:53:19 +04:00
2016-04-30 20:04:14 +03:00
links . push_back ( TextLinkData ( linkType , linkText , linkData , linkDisplayStatus ) ) ;
2015-10-23 18:06:56 +02:00
lnkIndex = 0x8000 + links . size ( ) ;
2014-05-30 12:53:19 +04:00
2016-04-30 20:04:14 +03:00
for ( auto entityEnd = start + waitingEntity - > offset ( ) + waitingEntity - > length ( ) ; ptr < entityEnd ; + + ptr ) {
parseCurrentChar ( ) ;
parseEmojiFromCurrent ( ) ;
2014-05-30 12:53:19 +04:00
2016-04-30 20:04:14 +03:00
if ( sumFinished | | _t - > _text . size ( ) > = 0x8000 ) break ; // 32k max
}
2015-10-23 18:06:56 +02:00
createBlock ( ) ;
lnkIndex = 0 ;
} else if ( startFlags ) {
if ( ! ( flags & startFlags ) ) {
createBlock ( ) ;
flags | = startFlags ;
2016-04-29 15:00:48 +03:00
removeFlags [ start + waitingEntity - > offset ( ) + waitingEntity - > length ( ) ] . push_front ( startFlags ) ;
2015-10-23 18:06:56 +02:00
}
}
+ + waitingEntity ;
if ( links . size ( ) > = 0x7FFF ) {
while ( waitingEntity ! = entitiesEnd & & (
2016-04-29 15:00:48 +03:00
waitingEntity - > type ( ) = = EntityInTextUrl | |
waitingEntity - > type ( ) = = EntityInTextCustomUrl | |
waitingEntity - > type ( ) = = EntityInTextEmail | |
waitingEntity - > type ( ) = = EntityInTextHashtag | |
waitingEntity - > type ( ) = = EntityInTextMention | |
waitingEntity - > type ( ) = = EntityInTextMentionName | |
waitingEntity - > type ( ) = = EntityInTextBotCommand | |
waitingEntity - > length ( ) < = 0 ) ) {
2015-10-23 18:06:56 +02:00
+ + waitingEntity ;
}
} else {
2016-04-29 15:00:48 +03:00
while ( waitingEntity ! = entitiesEnd & & waitingEntity - > length ( ) < = 0 ) + + waitingEntity ;
2015-10-23 18:06:56 +02:00
}
2016-05-04 19:46:24 +03:00
return true ;
2014-05-30 12:53:19 +04:00
}
2016-01-07 17:49:54 +08:00
2016-02-13 16:08:28 +03:00
bool readSkipBlockCommand ( ) {
const QChar * afterCmd = textSkipCommand ( ptr , end , links . size ( ) < 0x7FFF ) ;
if ( afterCmd = = ptr ) {
return false ;
}
ushort cmd = ( + + ptr ) - > unicode ( ) ;
+ + ptr ;
switch ( cmd ) {
case TextCommandSkipBlock :
createSkipBlock ( ptr - > unicode ( ) , ( ptr + 1 ) - > unicode ( ) ) ;
break ;
}
ptr = afterCmd ;
return true ;
}
2014-05-30 12:53:19 +04:00
bool readCommand ( ) {
2014-12-18 21:40:49 +03:00
const QChar * afterCmd = textSkipCommand ( ptr , end , links . size ( ) < 0x7FFF ) ;
2014-05-30 12:53:19 +04:00
if ( afterCmd = = ptr ) {
return false ;
}
ushort cmd = ( + + ptr ) - > unicode ( ) ;
+ + ptr ;
switch ( cmd ) {
case TextCommandBold :
2015-10-23 18:06:56 +02:00
if ( ! ( flags & TextBlockFBold ) ) {
2014-05-30 12:53:19 +04:00
createBlock ( ) ;
2015-10-23 18:06:56 +02:00
flags | = TextBlockFBold ;
2014-05-30 12:53:19 +04:00
}
break ;
case TextCommandNoBold :
2015-10-23 18:06:56 +02:00
if ( flags & TextBlockFBold ) {
2014-05-30 12:53:19 +04:00
createBlock ( ) ;
2015-10-23 18:06:56 +02:00
flags & = ~ TextBlockFBold ;
2014-05-30 12:53:19 +04:00
}
break ;
2016-03-05 00:04:15 +02:00
case TextCommandSemibold :
if ( ! ( flags & TextBlockFSemibold ) ) {
createBlock ( ) ;
flags | = TextBlockFSemibold ;
}
break ;
case TextCommandNoSemibold :
if ( flags & TextBlockFSemibold ) {
createBlock ( ) ;
flags & = ~ TextBlockFSemibold ;
}
break ;
2014-05-30 12:53:19 +04:00
case TextCommandItalic :
2015-10-23 18:06:56 +02:00
if ( ! ( flags & TextBlockFItalic ) ) {
2014-05-30 12:53:19 +04:00
createBlock ( ) ;
2015-10-23 18:06:56 +02:00
flags | = TextBlockFItalic ;
2014-05-30 12:53:19 +04:00
}
break ;
case TextCommandNoItalic :
2015-10-23 18:06:56 +02:00
if ( flags & TextBlockFItalic ) {
2014-05-30 12:53:19 +04:00
createBlock ( ) ;
2015-10-23 18:06:56 +02:00
flags & = ~ TextBlockFItalic ;
2014-05-30 12:53:19 +04:00
}
break ;
case TextCommandUnderline :
2015-10-23 18:06:56 +02:00
if ( ! ( flags & TextBlockFUnderline ) ) {
2014-05-30 12:53:19 +04:00
createBlock ( ) ;
2015-10-23 18:06:56 +02:00
flags | = TextBlockFUnderline ;
2014-05-30 12:53:19 +04:00
}
break ;
case TextCommandNoUnderline :
2015-10-23 18:06:56 +02:00
if ( flags & TextBlockFUnderline ) {
2014-05-30 12:53:19 +04:00
createBlock ( ) ;
2015-10-23 18:06:56 +02:00
flags & = ~ TextBlockFUnderline ;
2014-05-30 12:53:19 +04:00
}
break ;
case TextCommandLinkIndex :
if ( ptr - > unicode ( ) ! = lnkIndex ) {
createBlock ( ) ;
lnkIndex = ptr - > unicode ( ) ;
}
break ;
case TextCommandLinkText : {
createBlock ( ) ;
int32 len = ptr - > unicode ( ) ;
2016-04-30 20:04:14 +03:00
links . push_back ( TextLinkData ( EntityInTextCustomUrl , QString ( ) , QString ( + + ptr , len ) , LinkDisplayedFull ) ) ;
2014-05-30 12:53:19 +04:00
lnkIndex = 0x8000 + links . size ( ) ;
} break ;
case TextCommandSkipBlock :
createSkipBlock ( ptr - > unicode ( ) , ( ptr + 1 ) - > unicode ( ) ) ;
break ;
}
ptr = afterCmd ;
return true ;
}
void parseCurrentChar ( ) {
int skipBack = 0 ;
2015-10-23 18:06:56 +02:00
ch = ( ( ptr < end ) ? * ptr : 0 ) ;
2015-11-27 20:10:16 +03:00
emojiLookback = 0 ;
2015-11-04 18:46:01 -05:00
bool skip = false , isNewLine = multiline & & chIsNewline ( ch ) , isSpace = chIsSpace ( ch ) , isDiac = chIsDiac ( ch ) , isTilde = checkTilde & & ( ch = = ' ~ ' ) ;
2014-05-30 12:53:19 +04:00
if ( chIsBad ( ch ) | | ch . isLowSurrogate ( ) ) {
skip = true ;
2016-06-08 21:46:37 +03:00
} else if ( ch = = 0xFE0F & & ( cPlatform ( ) = = dbipMac | | cPlatform ( ) = = dbipMacOld ) ) {
// Some sequences like 0x0E53 0xFE0F crash OS X harfbuzz text processing :(
skip = true ;
2014-05-30 12:53:19 +04:00
} else if ( isDiac ) {
2015-03-12 13:28:10 +03:00
if ( lastSkipped | | emoji | | + + diacs > chMaxDiacAfterSymbol ( ) ) {
2014-05-30 12:53:19 +04:00
skip = true ;
}
} else if ( ch . isHighSurrogate ( ) ) {
if ( ptr + 1 > = end | | ! ( ptr + 1 ) - > isLowSurrogate ( ) ) {
skip = true ;
} else {
_t - > _text . push_back ( ch ) ;
skipBack = - 1 ;
+ + ptr ;
ch = * ptr ;
2015-11-27 20:10:16 +03:00
emojiLookback = 1 ;
2014-05-30 12:53:19 +04:00
}
}
lastSkipped = skip ;
if ( skip ) {
ch = 0 ;
} else {
2015-10-27 23:43:42 -04:00
if ( isTilde ) { // tilde fix in OpenSans
2015-10-23 18:06:56 +02:00
if ( ! ( flags & TextBlockFTilde ) ) {
2015-11-04 18:46:01 -05:00
createBlock ( skipBack ) ;
2015-10-23 18:06:56 +02:00
flags | = TextBlockFTilde ;
}
2015-10-27 23:43:42 -04:00
} else {
2015-10-23 18:06:56 +02:00
if ( flags & TextBlockFTilde ) {
2015-11-04 18:46:01 -05:00
createBlock ( skipBack ) ;
2015-10-23 18:06:56 +02:00
flags & = ~ TextBlockFTilde ;
}
}
2014-05-30 12:53:19 +04:00
if ( isNewLine ) {
2015-10-23 18:06:56 +02:00
createNewlineBlock ( ) ;
2014-05-30 12:53:19 +04:00
} else if ( isSpace ) {
_t - > _text . push_back ( QChar : : Space ) ;
} else {
if ( emoji ) createBlock ( skipBack ) ;
_t - > _text . push_back ( ch ) ;
}
if ( ! isDiac ) diacs = 0 ;
}
}
void parseEmojiFromCurrent ( ) {
2015-11-27 20:10:16 +03:00
int len = 0 ;
2017-02-15 11:50:11 +03:00
auto e = Ui : : Emoji : : Find ( ptr - emojiLookback , end , & len ) ;
2014-05-30 12:53:19 +04:00
if ( ! e ) return ;
2015-11-27 20:10:16 +03:00
for ( int l = len - emojiLookback - 1 ; l > 0 ; - - l ) {
2014-11-24 00:49:14 +03:00
_t - > _text . push_back ( * + + ptr ) ;
}
2017-02-15 11:50:11 +03:00
if ( e - > hasPostfix ( ) & & _t - > _text . at ( _t - > _text . size ( ) - 1 ) . unicode ( ) ! = Ui : : Emoji : : kPostfix ) {
_t - > _text . push_back ( QChar ( Ui : : Emoji : : kPostfix ) ) ;
2015-05-12 18:01:49 +03:00
+ + len ;
}
2014-05-30 12:53:19 +04:00
2015-05-08 15:45:14 +03:00
createBlock ( - len ) ;
2014-05-30 12:53:19 +04:00
emoji = e ;
}
2015-08-24 13:53:04 +03:00
TextParser ( Text * t , const QString & text , const TextParseOptions & options ) : _t ( t ) ,
2017-07-06 14:37:42 +03:00
source { text } ,
2015-08-24 13:53:04 +03:00
rich ( options . flags & TextParseRichText ) ,
multiline ( options . flags & TextParseMultiline ) ,
stopAfterWidth ( QFIXED_MAX ) {
if ( options . flags & TextParseLinks ) {
2017-07-06 14:37:42 +03:00
TextUtilities : : ParseEntities ( source , options . flags , rich ) ;
2015-08-24 13:53:04 +03:00
}
parse ( options ) ;
}
2017-02-16 17:07:16 +03:00
2016-05-06 20:33:48 +03:00
TextParser ( Text * t , const TextWithEntities & textWithEntities , const TextParseOptions & options ) : _t ( t ) ,
2017-07-06 14:37:42 +03:00
source ( textWithEntities ) ,
2015-08-24 13:53:04 +03:00
rich ( options . flags & TextParseRichText ) ,
multiline ( options . flags & TextParseMultiline ) ,
stopAfterWidth ( QFIXED_MAX ) {
2017-07-06 14:37:42 +03:00
auto & preparsed = textWithEntities . entities ;
2015-10-23 18:06:56 +02:00
if ( ( options . flags & TextParseLinks ) & & ! preparsed . isEmpty ( ) ) {
2015-08-31 17:27:20 +03:00
bool parseMentions = ( options . flags & TextParseMentions ) ;
bool parseHashtags = ( options . flags & TextParseHashtags ) ;
bool parseBotCommands = ( options . flags & TextParseBotCommands ) ;
2017-07-06 16:44:11 +03:00
bool parseMarkdown = ( options . flags & TextParseMarkdown ) ;
if ( ! parseMentions | | ! parseHashtags | | ! parseBotCommands | | ! parseMarkdown ) {
2015-10-23 18:06:56 +02:00
int32 i = 0 , l = preparsed . size ( ) ;
2017-07-06 14:37:42 +03:00
source . entities . clear ( ) ;
source . entities . reserve ( l ) ;
const QChar s = source . text . size ( ) ;
2015-08-31 17:27:20 +03:00
for ( ; i < l ; + + i ) {
2016-04-29 15:00:48 +03:00
auto type = preparsed . at ( i ) . type ( ) ;
if ( ( ( type = = EntityInTextMention | | type = = EntityInTextMentionName ) & & ! parseMentions ) | |
( type = = EntityInTextHashtag & & ! parseHashtags ) | |
( type = = EntityInTextBotCommand & & ! parseBotCommands ) | |
2017-07-06 16:44:11 +03:00
( ( type = = EntityInTextBold | | type = = EntityInTextItalic | | type = = EntityInTextCode | | type = = EntityInTextPre ) & & ! parseMarkdown ) ) {
2015-08-31 17:27:20 +03:00
continue ;
}
2017-07-06 14:37:42 +03:00
source . entities . push_back ( preparsed . at ( i ) ) ;
2015-08-31 17:27:20 +03:00
}
}
}
2015-08-24 13:53:04 +03:00
parse ( options ) ;
}
2017-02-16 17:07:16 +03:00
2015-08-24 13:53:04 +03:00
void parse ( const TextParseOptions & options ) {
2014-05-30 12:53:19 +04:00
if ( options . maxw > 0 & & options . maxh > 0 ) {
2016-12-23 16:21:01 +03:00
stopAfterWidth = ( ( options . maxh / _t - > _st - > font - > height ) + 1 ) * options . maxw ;
2014-05-30 12:53:19 +04:00
}
2017-07-06 14:37:42 +03:00
start = source . text . constData ( ) ;
end = start + source . text . size ( ) ;
2014-05-30 12:53:19 +04:00
2017-07-06 14:37:42 +03:00
entitiesEnd = source . entities . cend ( ) ;
waitingEntity = source . entities . cbegin ( ) ;
2016-04-29 15:00:48 +03:00
while ( waitingEntity ! = entitiesEnd & & waitingEntity - > length ( ) < = 0 ) + + waitingEntity ;
2017-07-06 14:37:42 +03:00
int firstMonospaceOffset = EntityInText : : firstMonospaceOffset ( source . entities , end - start ) ;
2016-04-29 15:00:48 +03:00
2015-08-28 19:30:32 +03:00
ptr = start ;
2016-04-29 15:00:48 +03:00
while ( ptr ! = end & & chIsTrimmed ( * ptr , rich ) & & ptr ! = start + firstMonospaceOffset ) {
2015-08-28 19:30:32 +03:00
+ + ptr ;
2014-05-30 12:53:19 +04:00
}
2015-08-28 19:30:32 +03:00
while ( ptr ! = end & & chIsTrimmed ( * ( end - 1 ) , rich ) ) {
2014-05-30 12:53:19 +04:00
- - end ;
}
_t - > _text . resize ( 0 ) ;
2015-08-28 19:30:32 +03:00
_t - > _text . reserve ( end - ptr ) ;
2014-05-30 12:53:19 +04:00
diacs = 0 ;
sumWidth = 0 ;
2015-10-23 18:06:56 +02:00
sumFinished = newlineAwaited = false ;
2014-05-30 12:53:19 +04:00
blockStart = 0 ;
emoji = 0 ;
2015-11-27 20:10:16 +03:00
ch = emojiLookback = 0 ;
2014-05-30 12:53:19 +04:00
lastSkipped = false ;
2017-04-30 19:03:12 +03:00
checkTilde = ! cRetina ( ) & & ( _t - > _st - > font - > size ( ) = = 13 ) & & ( _t - > _st - > font - > flags ( ) = = 0 ) & & ( _t - > _st - > font - > f . family ( ) = = qstr ( " Open Sans " ) ) ; // tilde Open Sans fix
2015-08-28 19:30:32 +03:00
for ( ; ptr < = end ; + + ptr ) {
2016-05-04 19:46:24 +03:00
while ( checkEntities ( ) | | ( rich & & checkCommand ( ) ) ) {
2015-11-23 18:34:38 +03:00
}
2014-05-30 12:53:19 +04:00
parseCurrentChar ( ) ;
parseEmojiFromCurrent ( ) ;
if ( sumFinished | | _t - > _text . size ( ) > = 0x8000 ) break ; // 32k max
}
createBlock ( ) ;
2016-02-13 16:08:28 +03:00
if ( sumFinished & & rich ) { // we could've skipped the final skip block command
for ( ; ptr < end ; + + ptr ) {
if ( * ptr = = TextCommand & & readSkipBlockCommand ( ) ) {
break ;
}
}
}
2015-10-23 18:06:56 +02:00
removeFlags . clear ( ) ;
2014-05-30 12:53:19 +04:00
_t - > _links . resize ( maxLnkIndex ) ;
2017-07-06 14:37:42 +03:00
for ( auto i = _t - > _blocks . cbegin ( ) , e = _t - > _blocks . cend ( ) ; i ! = e ; + + i ) {
2017-07-06 18:58:05 +03:00
auto b = i - > get ( ) ;
2014-05-30 12:53:19 +04:00
if ( b - > lnkIndex ( ) > 0x8000 ) {
lnkIndex = maxLnkIndex + ( b - > lnkIndex ( ) - 0x8000 ) ;
if ( _t - > _links . size ( ) < lnkIndex ) {
_t - > _links . resize ( lnkIndex ) ;
2017-07-06 14:37:42 +03:00
auto & link = links [ lnkIndex - maxLnkIndex - 1 ] ;
2016-04-30 20:04:14 +03:00
ClickHandlerPtr handler ;
switch ( link . type ) {
2017-09-13 20:01:23 +03:00
case EntityInTextCustomUrl : {
if ( ! link . data . isEmpty ( ) ) {
handler = MakeShared < HiddenUrlClickHandler > ( link . data ) ;
}
} break ;
2016-04-30 20:04:14 +03:00
case EntityInTextEmail :
2017-07-06 14:37:42 +03:00
case EntityInTextUrl : handler = MakeShared < UrlClickHandler > ( link . data , link . displayStatus = = LinkDisplayedFull ) ; break ;
case EntityInTextBotCommand : handler = MakeShared < BotCommandClickHandler > ( link . data ) ; break ;
2016-04-30 20:04:14 +03:00
case EntityInTextHashtag :
2015-04-08 02:03:32 +03:00
if ( options . flags & TextTwitterMentions ) {
2017-07-06 14:37:42 +03:00
handler = MakeShared < UrlClickHandler > ( qsl ( " https://twitter.com/hashtag/ " ) + link . data . mid ( 1 ) + qsl ( " ?src=hash " ) , true ) ;
2015-04-08 02:03:32 +03:00
} else if ( options . flags & TextInstagramMentions ) {
2017-07-06 14:37:42 +03:00
handler = MakeShared < UrlClickHandler > ( qsl ( " https://instagram.com/explore/tags/ " ) + link . data . mid ( 1 ) + ' / ' , true ) ;
2015-04-08 02:03:32 +03:00
} else {
2017-07-06 14:37:42 +03:00
handler = MakeShared < HashtagClickHandler > ( link . data ) ;
2015-04-08 02:03:32 +03:00
}
2016-04-30 20:04:14 +03:00
break ;
case EntityInTextMention :
2015-04-08 02:03:32 +03:00
if ( options . flags & TextTwitterMentions ) {
2017-07-06 14:37:42 +03:00
handler = MakeShared < UrlClickHandler > ( qsl ( " https://twitter.com/ " ) + link . data . mid ( 1 ) , true ) ;
2015-04-08 02:03:32 +03:00
} else if ( options . flags & TextInstagramMentions ) {
2017-07-06 14:37:42 +03:00
handler = MakeShared < UrlClickHandler > ( qsl ( " https://instagram.com/ " ) + link . data . mid ( 1 ) + ' / ' , true ) ;
2016-04-30 20:04:14 +03:00
} else {
2017-07-06 14:37:42 +03:00
handler = MakeShared < MentionClickHandler > ( link . data ) ;
2016-04-30 20:04:14 +03:00
}
break ;
case EntityInTextMentionName : {
2017-07-06 14:37:42 +03:00
auto fields = TextUtilities : : MentionNameDataToFields ( link . data ) ;
if ( fields . userId ) {
handler = MakeShared < MentionNameClickHandler > ( link . text , fields . userId , fields . accessHash ) ;
2015-04-08 02:03:32 +03:00
} else {
2016-04-30 20:04:14 +03:00
LOG ( ( " Bad mention name: %1 " ) . arg ( link . data ) ) ;
2015-04-08 02:03:32 +03:00
}
2016-04-30 20:04:14 +03:00
} break ;
2014-05-30 12:53:19 +04:00
}
2016-04-30 20:04:14 +03:00
2017-07-06 14:37:42 +03:00
if ( ! handler . isNull ( ) ) {
_t - > setLink ( lnkIndex , handler ) ;
}
2014-05-30 12:53:19 +04:00
}
b - > setLnkIndex ( lnkIndex ) ;
}
}
_t - > _links . squeeze ( ) ;
2017-07-06 18:58:05 +03:00
_t - > _blocks . shrink_to_fit ( ) ;
2014-05-30 12:53:19 +04:00
_t - > _text . squeeze ( ) ;
}
private :
2016-04-30 20:04:14 +03:00
enum LinkDisplayStatus {
LinkDisplayedFull ,
LinkDisplayedElided ,
} ;
2017-02-16 17:07:16 +03:00
2016-04-30 20:04:14 +03:00
struct TextLinkData {
TextLinkData ( ) = default ;
TextLinkData ( EntityInTextType type , const QString & text , const QString & data , LinkDisplayStatus displayStatus )
: type ( type )
, text ( text )
, data ( data )
, displayStatus ( displayStatus ) {
}
EntityInTextType type = EntityInTextInvalid ;
QString text , data ;
LinkDisplayStatus displayStatus = LinkDisplayedFull ;
} ;
void computeLinkText ( const QString & linkData , QString * outLinkText , LinkDisplayStatus * outDisplayStatus ) {
2017-11-11 21:53:23 +04:00
auto url = QUrl ( linkData ) ;
auto good = QUrl ( url . isValid ( )
? url . toEncoded ( )
: QByteArray ( ) ) ;
auto readable = good . isValid ( )
? good . toDisplayString ( )
: linkData ;
2016-12-23 16:21:01 +03:00
* outLinkText = _t - > _st - > font - > elided ( readable , st : : linkCropLimit ) ;
2016-04-30 20:04:14 +03:00
* outDisplayStatus = ( * outLinkText = = readable ) ? LinkDisplayedFull : LinkDisplayedElided ;
}
2014-05-30 12:53:19 +04:00
Text * _t ;
2017-07-06 14:37:42 +03:00
TextWithEntities source ;
2014-05-30 12:53:19 +04:00
const QChar * start , * end , * ptr ;
bool rich , multiline ;
2015-10-23 18:06:56 +02:00
EntitiesInText : : const_iterator waitingEntity , entitiesEnd ;
2014-05-30 12:53:19 +04:00
typedef QVector < TextLinkData > TextLinks ;
TextLinks links ;
2015-10-23 18:06:56 +02:00
typedef QMap < const QChar * , QList < int32 > > RemoveFlagsMap ;
RemoveFlagsMap removeFlags ;
2017-02-15 11:50:11 +03:00
uint16 maxLnkIndex = 0 ;
2014-05-30 12:53:19 +04:00
// current state
2017-02-15 11:50:11 +03:00
int32 flags = 0 ;
uint16 lnkIndex = 0 ;
EmojiPtr emoji = nullptr ; // current emoji, if current word is an emoji, or zero
int32 blockStart = 0 ; // offset in result, from which current parsed block is started
int32 diacs = 0 ; // diac chars skipped without good char
2014-05-30 12:53:19 +04:00
QFixed sumWidth , stopAfterWidth ; // summary width of all added words
2017-02-15 11:50:11 +03:00
bool sumFinished = false ;
bool newlineAwaited = false ;
2014-05-30 12:53:19 +04:00
// current char data
QChar ch ; // current char (low surrogate, if current char is surrogate pair)
2015-11-27 20:10:16 +03:00
int32 emojiLookback ; // how far behind the current ptr to look for current emoji
2014-05-30 12:53:19 +04:00
bool lastSkipped ; // did we skip current char
2015-11-04 18:46:01 -05:00
bool checkTilde ; // do we need a special text block for tilde symbol
2014-05-30 12:53:19 +04:00
} ;
namespace {
2017-02-16 17:07:16 +03:00
// COPIED FROM qtextengine.cpp AND MODIFIED
struct BidiStatus {
BidiStatus ( ) {
eor = QChar : : DirON ;
lastStrong = QChar : : DirON ;
last = QChar : : DirON ;
dir = QChar : : DirON ;
}
QChar : : Direction eor ;
QChar : : Direction lastStrong ;
QChar : : Direction last ;
QChar : : Direction dir ;
} ;
2014-05-30 12:53:19 +04:00
2017-02-16 17:07:16 +03:00
enum { _MaxBidiLevel = 61 } ;
enum { _MaxItemLength = 4096 } ;
2014-05-30 12:53:19 +04:00
2017-02-16 17:07:16 +03:00
struct BidiControl {
inline BidiControl ( bool rtl )
2017-09-17 14:53:38 +03:00
: base ( rtl ? 1 : 0 ) , level ( rtl ? 1 : 0 ) { }
2014-05-30 12:53:19 +04:00
2017-02-16 17:07:16 +03:00
inline void embed ( bool rtl , bool o = false ) {
unsigned int toAdd = 1 ;
if ( ( level % 2 ! = 0 ) = = rtl ) {
+ + toAdd ;
2014-05-30 12:53:19 +04:00
}
2017-02-16 17:07:16 +03:00
if ( level + toAdd < = _MaxBidiLevel ) {
ctx [ cCtx ] . level = level ;
ctx [ cCtx ] . override = override ;
cCtx + + ;
override = o ;
level + = toAdd ;
2014-05-30 12:53:19 +04:00
}
2017-02-16 17:07:16 +03:00
}
inline bool canPop ( ) const { return cCtx ! = 0 ; }
inline void pdf ( ) {
Q_ASSERT ( cCtx ) ;
- - cCtx ;
level = ctx [ cCtx ] . level ;
override = ctx [ cCtx ] . override ;
}
2014-05-30 12:53:19 +04:00
2017-02-16 17:07:16 +03:00
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 ) ;
}
2014-05-30 12:53:19 +04:00
2017-02-16 17:07:16 +03:00
struct {
2017-09-17 14:53:38 +03:00
unsigned int level = 0 ;
bool override = false ;
2017-02-16 17:07:16 +03:00
} ctx [ _MaxBidiLevel ] ;
2017-09-17 14:53:38 +03:00
unsigned int cCtx = 0 ;
2017-02-16 17:07:16 +03:00
const unsigned int base ;
unsigned int level ;
2017-09-17 14:53:38 +03:00
bool override = false ;
2017-02-16 17:07:16 +03:00
} ;
2014-05-30 12:53:19 +04:00
2017-02-16 17:07:16 +03:00
static void eAppendItems ( QScriptAnalysis * analysis , int & start , int & stop , const BidiControl & control , QChar : : Direction dir ) {
if ( start > stop )
return ;
2014-05-30 12:53:19 +04:00
2017-02-16 17:07:16 +03:00
int level = control . level ;
2014-05-30 12:53:19 +04:00
2017-02-16 17:07:16 +03:00
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 ;
2014-05-30 12:53:19 +04:00
}
2017-02-16 17:07:16 +03:00
}
2014-05-30 12:53:19 +04:00
2017-02-16 17:07:16 +03:00
QScriptAnalysis * s = analysis + start ;
const QScriptAnalysis * e = analysis + stop ;
while ( s < = e ) {
s - > bidiLevel = level ;
+ + s ;
2014-05-30 12:53:19 +04:00
}
2017-02-16 17:07:16 +03:00
+ + stop ;
start = stop ;
2014-05-30 12:53:19 +04:00
}
2017-02-16 17:07:16 +03:00
} // namespace
2014-05-30 12:53:19 +04:00
class TextPainter {
public :
2016-12-23 16:21:01 +03:00
TextPainter ( Painter * p , const Text * t ) : _p ( p ) , _t ( t ) {
2014-05-30 12:53:19 +04:00
}
2016-06-08 19:40:33 +03:00
~ TextPainter ( ) {
restoreAfterElided ( ) ;
2017-02-16 17:07:16 +03:00
if ( _p ) {
_p - > setPen ( _originalPen ) ;
2014-05-30 12:53:19 +04:00
}
}
2016-06-09 14:51:24 +03:00
void draw ( int32 left , int32 top , int32 w , style : : align align , int32 yFrom , int32 yTo , TextSelection selection = { 0 , 0 } , bool fullWidthSelection = true ) {
2015-06-25 13:12:38 +03:00
if ( _t - > isEmpty ( ) ) return ;
2014-05-30 12:53:19 +04:00
_blocksSize = _t - > _blocks . size ( ) ;
if ( _p ) {
2016-12-23 16:21:01 +03:00
_p - > setFont ( _t - > _st - > font ) ;
2017-02-16 17:07:16 +03:00
_textPalette = & _p - > textPalette ( ) ;
2014-05-30 12:53:19 +04:00
_originalPen = _p - > pen ( ) ;
2017-02-16 17:07:16 +03:00
_originalPenSelected = ( _textPalette - > selectFg - > c . alphaF ( ) = = 0 ) ? _originalPen : _textPalette - > selectFg - > p ;
2014-05-30 12:53:19 +04:00
}
_x = left ;
_y = top ;
_yFrom = yFrom + top ;
_yTo = ( yTo < 0 ) ? - 1 : ( yTo + top ) ;
2016-04-13 21:29:32 +03:00
_selection = selection ;
2016-06-09 14:51:24 +03:00
_fullWidthSelection = fullWidthSelection ;
2014-05-30 12:53:19 +04:00
_wLeft = _w = w ;
2016-06-17 16:27:21 +03:00
if ( _elideLast ) {
_yToElide = _yTo ;
2017-07-06 18:58:05 +03:00
if ( _elideRemoveFromEnd > 0 & & ! _t - > _blocks . empty ( ) ) {
int firstBlockHeight = countBlockHeight ( _t - > _blocks . front ( ) . get ( ) , _t - > _st ) ;
2016-06-17 16:27:21 +03:00
if ( _y + firstBlockHeight > = _yToElide ) {
_wLeft - = _elideRemoveFromEnd ;
}
}
}
2014-05-30 12:53:19 +04:00
_str = _t - > _text . unicode ( ) ;
if ( _p ) {
2017-02-16 17:07:16 +03:00
auto clip = _p - > hasClipping ( ) ? _p - > clipBoundingRect ( ) : QRect ( ) ;
2014-05-30 12:53:19 +04:00
if ( clip . width ( ) > 0 | | clip . height ( ) > 0 ) {
if ( _yFrom < clip . y ( ) ) _yFrom = clip . y ( ) ;
if ( _yTo < 0 | | _yTo > clip . y ( ) + clip . height ( ) ) _yTo = clip . y ( ) + clip . height ( ) ;
}
}
_align = align ;
_parDirection = _t - > _startDir ;
2015-04-19 13:29:19 +03:00
if ( _parDirection = = Qt : : LayoutDirectionAuto ) _parDirection = cLangDir ( ) ;
2015-10-23 18:06:56 +02:00
if ( ( * _t - > _blocks . cbegin ( ) ) - > type ( ) ! = TextBlockTNewline ) {
2014-05-30 12:53:19 +04:00
initNextParagraph ( _t - > _blocks . cbegin ( ) ) ;
}
_lineStart = 0 ;
_lineStartBlock = 0 ;
_lineHeight = 0 ;
2016-12-23 16:21:01 +03:00
_fontHeight = _t - > _st - > font - > height ;
2017-02-26 19:20:17 +03:00
auto last_rBearing = QFixed ( 0 ) ;
_last_rPadding = QFixed ( 0 ) ;
2014-05-30 12:53:19 +04:00
2017-02-26 19:20:17 +03:00
auto blockIndex = 0 ;
2014-05-30 12:53:19 +04:00
bool longWordLine = true ;
2017-02-26 19:20:17 +03:00
auto e = _t - > _blocks . cend ( ) ;
for ( auto i = _t - > _blocks . cbegin ( ) ; i ! = e ; + + i , + + blockIndex ) {
2017-07-06 18:58:05 +03:00
auto b = i - > get ( ) ;
2017-02-26 19:20:17 +03:00
auto _btype = b - > type ( ) ;
auto blockHeight = countBlockHeight ( b , _t - > _st ) ;
2014-05-30 12:53:19 +04:00
2015-10-23 18:06:56 +02:00
if ( _btype = = TextBlockTNewline ) {
2014-05-30 12:53:19 +04:00
if ( ! _lineHeight ) _lineHeight = blockHeight ;
2017-02-20 23:48:17 +03:00
if ( ! drawLine ( ( * i ) - > from ( ) , i , e ) ) {
2017-02-16 17:07:16 +03:00
return ;
}
2014-05-30 12:53:19 +04:00
_y + = _lineHeight ;
_lineHeight = 0 ;
2017-02-20 23:48:17 +03:00
_lineStart = _t - > countBlockEnd ( i , e ) ;
2014-05-30 12:53:19 +04:00
_lineStartBlock = blockIndex + 1 ;
2016-05-02 16:42:09 +03:00
last_rBearing = b - > f_rbearing ( ) ;
2017-02-26 19:20:17 +03:00
_last_rPadding = b - > f_rpadding ( ) ;
2014-05-30 12:53:19 +04:00
_wLeft = _w - ( b - > f_width ( ) - last_rBearing ) ;
2015-12-31 15:57:11 +08:00
if ( _elideLast & & _elideRemoveFromEnd > 0 & & ( _y + blockHeight > = _yToElide ) ) {
2015-04-04 23:01:34 +03:00
_wLeft - = _elideRemoveFromEnd ;
}
2014-05-30 12:53:19 +04:00
_parDirection = static_cast < NewlineBlock * > ( b ) - > nextDirection ( ) ;
2015-04-19 13:29:19 +03:00
if ( _parDirection = = Qt : : LayoutDirectionAuto ) _parDirection = cLangDir ( ) ;
2014-05-30 12:53:19 +04:00
initNextParagraph ( i + 1 ) ;
longWordLine = true ;
continue ;
}
2016-05-02 16:42:09 +03:00
auto b__f_rbearing = b - > f_rbearing ( ) ;
2017-02-26 19:20:17 +03:00
auto newWidthLeft = _wLeft - last_rBearing - ( _last_rPadding + b - > f_width ( ) - b__f_rbearing ) ;
2014-05-30 12:53:19 +04:00
if ( newWidthLeft > = 0 ) {
2016-05-02 16:42:09 +03:00
last_rBearing = b__f_rbearing ;
2017-02-26 19:20:17 +03:00
_last_rPadding = b - > f_rpadding ( ) ;
2014-05-30 12:53:19 +04:00
_wLeft = newWidthLeft ;
_lineHeight = qMax ( _lineHeight , blockHeight ) ;
longWordLine = false ;
continue ;
}
2015-10-23 18:06:56 +02:00
if ( _btype = = TextBlockTText ) {
2017-02-26 19:20:17 +03:00
auto t = static_cast < TextBlock * > ( b ) ;
2016-01-08 11:25:50 +08:00
if ( t - > _words . isEmpty ( ) ) { // no words in this block, spaces only => layout this block in the same line
2017-02-26 19:20:17 +03:00
_last_rPadding + = b - > f_rpadding ( ) ;
2016-01-08 11:25:50 +08:00
_lineHeight = qMax ( _lineHeight , blockHeight ) ;
longWordLine = false ;
continue ;
}
2017-02-26 19:20:17 +03:00
auto f_wLeft = _wLeft ; // vars for saving state of the last word start
auto f_lineHeight = _lineHeight ; // f points to the last word-start element of t->_words
for ( auto j = t - > _words . cbegin ( ) , en = t - > _words . cend ( ) , f = j ; j ! = en ; + + j ) {
auto wordEndsHere = ( j - > f_width ( ) > = 0 ) ;
auto j_width = wordEndsHere ? j - > f_width ( ) : - j - > f_width ( ) ;
2014-05-30 12:53:19 +04:00
2017-02-26 19:20:17 +03:00
auto newWidthLeft = _wLeft - last_rBearing - ( _last_rPadding + j_width - j - > f_rbearing ( ) ) ;
2014-05-30 12:53:19 +04:00
if ( newWidthLeft > = 0 ) {
last_rBearing = j - > f_rbearing ( ) ;
2017-02-26 19:20:17 +03:00
_last_rPadding = j - > f_rpadding ( ) ;
2014-05-30 12:53:19 +04:00
_wLeft = newWidthLeft ;
_lineHeight = qMax ( _lineHeight , blockHeight ) ;
if ( wordEndsHere ) {
longWordLine = false ;
}
if ( wordEndsHere | | longWordLine ) {
2015-12-03 14:05:45 +03:00
f = j + 1 ;
2014-05-30 12:53:19 +04:00
f_wLeft = _wLeft ;
f_lineHeight = _lineHeight ;
}
continue ;
}
2017-02-26 19:20:17 +03:00
auto elidedLineHeight = qMax ( _lineHeight , blockHeight ) ;
auto elidedLine = _elideLast & & ( _y + elidedLineHeight > = _yToElide ) ;
2014-06-12 15:20:14 +04:00
if ( elidedLine ) {
_lineHeight = elidedLineHeight ;
2016-02-19 14:53:49 +03:00
} else if ( f ! = j & & ! _breakEverywhere ) {
2015-12-03 14:05:45 +03:00
// word did not fit completely, so we roll back the state to the beginning of this long word
2014-05-30 12:53:19 +04:00
j = f ;
_wLeft = f_wLeft ;
_lineHeight = f_lineHeight ;
2016-05-02 16:42:09 +03:00
j_width = ( j - > f_width ( ) > = 0 ) ? j - > f_width ( ) : - j - > f_width ( ) ;
2014-05-30 12:53:19 +04:00
}
2017-02-16 17:07:16 +03:00
if ( ! drawLine ( elidedLine ? ( ( j + 1 = = en ) ? _t - > countBlockEnd ( i , e ) : ( j + 1 ) - > from ( ) ) : j - > from ( ) , i , e ) ) {
return ;
}
2014-05-30 12:53:19 +04:00
_y + = _lineHeight ;
_lineHeight = qMax ( 0 , blockHeight ) ;
2016-05-02 16:42:09 +03:00
_lineStart = j - > from ( ) ;
2014-05-30 12:53:19 +04:00
_lineStartBlock = blockIndex ;
last_rBearing = j - > f_rbearing ( ) ;
2017-02-26 19:20:17 +03:00
_last_rPadding = j - > f_rpadding ( ) ;
2014-05-30 12:53:19 +04:00
_wLeft = _w - ( j_width - last_rBearing ) ;
2015-12-31 15:57:11 +08:00
if ( _elideLast & & _elideRemoveFromEnd > 0 & & ( _y + blockHeight > = _yToElide ) ) {
2015-04-04 23:01:34 +03:00
_wLeft - = _elideRemoveFromEnd ;
}
2014-05-30 12:53:19 +04:00
longWordLine = true ;
f = j + 1 ;
f_wLeft = _wLeft ;
f_lineHeight = _lineHeight ;
}
continue ;
}
2017-02-26 19:20:17 +03:00
auto elidedLineHeight = qMax ( _lineHeight , blockHeight ) ;
auto elidedLine = _elideLast & & ( _y + elidedLineHeight > = _yToElide ) ;
2014-06-12 15:20:14 +04:00
if ( elidedLine ) {
_lineHeight = elidedLineHeight ;
}
2017-02-16 17:07:16 +03:00
if ( ! drawLine ( elidedLine ? _t - > countBlockEnd ( i , e ) : b - > from ( ) , i , e ) ) {
return ;
}
2014-05-30 12:53:19 +04:00
_y + = _lineHeight ;
_lineHeight = qMax ( 0 , blockHeight ) ;
_lineStart = b - > from ( ) ;
_lineStartBlock = blockIndex ;
2016-05-02 16:42:09 +03:00
last_rBearing = b__f_rbearing ;
2017-02-26 19:20:17 +03:00
_last_rPadding = b - > f_rpadding ( ) ;
2014-05-30 12:53:19 +04:00
_wLeft = _w - ( b - > f_width ( ) - last_rBearing ) ;
2015-12-31 15:57:11 +08:00
if ( _elideLast & & _elideRemoveFromEnd > 0 & & ( _y + blockHeight > = _yToElide ) ) {
2015-04-04 23:01:34 +03:00
_wLeft - = _elideRemoveFromEnd ;
}
2014-05-30 12:53:19 +04:00
longWordLine = true ;
continue ;
}
if ( _lineStart < _t - > _text . size ( ) ) {
if ( ! drawLine ( _t - > _text . size ( ) , e , e ) ) return ;
}
2016-04-13 21:29:32 +03:00
if ( ! _p & & _lookupSymbol ) {
_lookupResult . symbol = _t - > _text . size ( ) ;
_lookupResult . afterSymbol = false ;
2014-05-30 12:53:19 +04:00
}
}
2016-04-13 21:29:32 +03:00
void drawElided ( int32 left , int32 top , int32 w , style : : align align , int32 lines , int32 yFrom , int32 yTo , int32 removeFromEnd , bool breakEverywhere , TextSelection selection ) {
2015-06-25 13:12:38 +03:00
if ( lines < = 0 | | _t - > isNull ( ) ) return ;
2014-05-30 12:53:19 +04:00
2016-12-23 16:21:01 +03:00
if ( yTo < 0 | | ( lines - 1 ) * _t - > _st - > font - > height < yTo ) {
yTo = lines * _t - > _st - > font - > height ;
2014-05-30 12:53:19 +04:00
_elideLast = true ;
2015-04-04 23:01:34 +03:00
_elideRemoveFromEnd = removeFromEnd ;
2014-05-30 12:53:19 +04:00
}
2016-02-19 14:53:49 +03:00
_breakEverywhere = breakEverywhere ;
2016-04-13 21:29:32 +03:00
draw ( left , top , w , align , yFrom , yTo , selection ) ;
2014-05-30 12:53:19 +04:00
}
2017-06-22 00:38:31 +03:00
Text : : StateResult getState ( QPoint point , int w , Text : : StateRequest request ) {
if ( ! _t - > isNull ( ) & & point . y ( ) > = 0 ) {
2016-04-13 21:29:32 +03:00
_lookupRequest = request ;
2017-06-22 00:38:31 +03:00
_lookupX = point . x ( ) ;
_lookupY = point . y ( ) ;
2016-04-13 21:29:32 +03:00
_breakEverywhere = ( _lookupRequest . flags & Text : : StateRequest : : Flag : : BreakEverywhere ) ;
_lookupSymbol = ( _lookupRequest . flags & Text : : StateRequest : : Flag : : LookupSymbol ) ;
_lookupLink = ( _lookupRequest . flags & Text : : StateRequest : : Flag : : LookupLink ) ;
if ( _lookupSymbol | | ( _lookupX > = 0 & & _lookupX < w ) ) {
draw ( 0 , 0 , w , _lookupRequest . align , _lookupY , _lookupY + 1 ) ;
}
}
return _lookupResult ;
}
2017-06-22 00:38:31 +03:00
Text : : StateResult getStateElided ( QPoint point , int w , Text : : StateRequestElided request ) {
if ( ! _t - > isNull ( ) & & point . y ( ) > = 0 & & request . lines > 0 ) {
2016-04-13 21:29:32 +03:00
_lookupRequest = request ;
2017-06-22 00:38:31 +03:00
_lookupX = point . x ( ) ;
_lookupY = point . y ( ) ;
2016-04-13 21:29:32 +03:00
_breakEverywhere = ( _lookupRequest . flags & Text : : StateRequest : : Flag : : BreakEverywhere ) ;
_lookupSymbol = ( _lookupRequest . flags & Text : : StateRequest : : Flag : : LookupSymbol ) ;
_lookupLink = ( _lookupRequest . flags & Text : : StateRequest : : Flag : : LookupLink ) ;
if ( _lookupSymbol | | ( _lookupX > = 0 & & _lookupX < w ) ) {
int yTo = _lookupY + 1 ;
2016-12-23 16:21:01 +03:00
if ( yTo < 0 | | ( request . lines - 1 ) * _t - > _st - > font - > height < yTo ) {
yTo = request . lines * _t - > _st - > font - > height ;
2016-04-13 21:29:32 +03:00
_elideLast = true ;
_elideRemoveFromEnd = request . removeFromEnd ;
}
draw ( 0 , 0 , w , _lookupRequest . align , _lookupY , _lookupY + 1 ) ;
}
2014-05-30 12:53:19 +04:00
}
2016-04-13 21:29:32 +03:00
return _lookupResult ;
2014-05-30 12:53:19 +04:00
}
2017-02-16 17:07:16 +03:00
private :
void initNextParagraph ( Text : : TextBlocks : : const_iterator i ) {
_parStartBlock = i ;
Text : : TextBlocks : : const_iterator e = _t - > _blocks . cend ( ) ;
if ( i = = e ) {
_parStart = _t - > _text . size ( ) ;
_parLength = 0 ;
} else {
_parStart = ( * i ) - > from ( ) ;
for ( ; i ! = e ; + + i ) {
if ( ( * i ) - > type ( ) = = TextBlockTNewline ) {
break ;
}
}
_parLength = ( ( i = = e ) ? _t - > _text . size ( ) : ( * i ) - > from ( ) ) - _parStart ;
}
_parAnalysis . resize ( 0 ) ;
}
void initParagraphBidi ( ) {
if ( ! _parLength | | ! _parAnalysis . isEmpty ( ) ) return ;
Text : : TextBlocks : : const_iterator i = _parStartBlock , e = _t - > _blocks . cend ( ) , n = i + 1 ;
bool ignore = false ;
bool rtl = ( _parDirection = = Qt : : RightToLeft ) ;
if ( ! ignore & & ! rtl ) {
ignore = true ;
const ushort * start = reinterpret_cast < const ushort * > ( _str ) + _parStart ;
const ushort * curr = start ;
const ushort * end = start + _parLength ;
while ( curr < end ) {
while ( n ! = e & & ( * n ) - > from ( ) < = _parStart + ( curr - start ) ) {
i = n ;
+ + n ;
}
if ( ( * i ) - > type ( ) ! = TextBlockTEmoji & & * curr > = 0x590 ) {
ignore = false ;
break ;
}
+ + curr ;
}
}
_parAnalysis . resize ( _parLength ) ;
QScriptAnalysis * analysis = _parAnalysis . data ( ) ;
BidiControl control ( rtl ) ;
_parHasBidi = false ;
if ( ignore ) {
memset ( analysis , 0 , _parLength * sizeof ( QScriptAnalysis ) ) ;
if ( rtl ) {
for ( int i = 0 ; i < _parLength ; + + i )
analysis [ i ] . bidiLevel = 1 ;
_parHasBidi = true ;
}
} else {
_parHasBidi = eBidiItemize ( analysis , control ) ;
}
}
2014-05-30 12:53:19 +04:00
bool drawLine ( uint16 _lineEnd , const Text : : TextBlocks : : const_iterator & _endBlockIter , const Text : : TextBlocks : : const_iterator & _end ) {
_yDelta = ( _lineHeight - _fontHeight ) / 2 ;
2016-06-13 13:42:55 +03:00
if ( _yTo > = 0 & & ( _y + _yDelta > = _yTo | | _y > = _yTo ) ) return false ;
2016-05-26 18:31:20 +03:00
if ( _y + _yDelta + _fontHeight < = _yFrom ) {
if ( _lookupSymbol ) {
_lookupResult . symbol = ( _lineEnd > _lineStart ) ? ( _lineEnd - 1 ) : _lineStart ;
_lookupResult . afterSymbol = ( _lineEnd > _lineStart ) ? true : false ;
}
return true ;
}
2014-05-30 12:53:19 +04:00
2017-02-26 19:20:17 +03:00
// Trimming pending spaces, because they sometimes don't fit on the line.
// They also are not counted in the line width, they're in the right padding.
// Line width is a sum of block / word widths and paddings between them, without trailing one.
auto trimmedLineEnd = _lineEnd ;
for ( ; trimmedLineEnd > _lineStart ; - - trimmedLineEnd ) {
auto ch = _t - > _text [ trimmedLineEnd - 1 ] ;
if ( ch ! = QChar : : Space & & ch ! = QChar : : LineFeed ) {
break ;
}
}
2015-06-15 20:19:24 +03:00
2017-07-06 18:58:05 +03:00
auto _endBlock = ( _endBlockIter = = _end ) ? nullptr : _endBlockIter - > get ( ) ;
2017-02-26 19:20:17 +03:00
auto elidedLine = _elideLast & & ( _y + _lineHeight > = _yToElide ) ;
2016-06-13 13:42:55 +03:00
if ( elidedLine ) {
// If we decided to draw the last line elided only because of the skip block
// that did not fit on this line, we just draw the line till the very end.
// Skip block is ignored in the elided lines, instead "removeFromEnd" is used.
if ( _endBlock & & _endBlock - > type ( ) = = TextBlockTSkip ) {
_endBlock = nullptr ;
}
if ( ! _endBlock ) {
elidedLine = false ;
}
}
2014-05-30 12:53:19 +04:00
2017-02-26 19:20:17 +03:00
auto blockIndex = _lineStartBlock ;
2017-07-06 18:58:05 +03:00
auto currentBlock = _t - > _blocks [ blockIndex ] . get ( ) ;
auto nextBlock = ( + + blockIndex < _blocksSize ) ? _t - > _blocks [ blockIndex ] . get ( ) : nullptr ;
2015-06-15 20:19:24 +03:00
int32 delta = ( currentBlock - > from ( ) < _lineStart ? qMin ( _lineStart - currentBlock - > from ( ) , 2 ) : 0 ) ;
_localFrom = _lineStart - delta ;
2017-02-16 17:07:16 +03:00
int32 lineEnd = ( _endBlock & & _endBlock - > from ( ) < trimmedLineEnd & & ! elidedLine ) ? qMin ( uint16 ( trimmedLineEnd + 2 ) , _t - > countBlockEnd ( _endBlockIter , _end ) ) : trimmedLineEnd ;
2015-06-15 20:19:24 +03:00
2017-02-26 19:20:17 +03:00
auto lineText = _t - > _text . mid ( _localFrom , lineEnd - _localFrom ) ;
auto lineStart = delta ;
auto lineLength = trimmedLineEnd - _lineStart ;
2015-06-15 20:19:24 +03:00
if ( elidedLine ) {
initParagraphBidi ( ) ;
prepareElidedLine ( lineText , lineStart , lineLength , _endBlock ) ;
}
2017-02-26 19:20:17 +03:00
auto x = _x ;
2014-05-30 12:53:19 +04:00
if ( _align & Qt : : AlignHCenter ) {
2014-06-14 23:32:11 +04:00
x + = ( _wLeft / 2 ) . toInt ( ) ;
} else if ( ( ( _align & Qt : : AlignLeft ) & & _parDirection = = Qt : : RightToLeft ) | | ( ( _align & Qt : : AlignRight ) & & _parDirection = = Qt : : LeftToRight ) ) {
2014-05-30 12:53:19 +04:00
x + = _wLeft ;
}
2016-04-13 21:29:32 +03:00
if ( ! _p ) {
if ( _lookupX < x ) {
if ( _lookupSymbol ) {
if ( _parDirection = = Qt : : RightToLeft ) {
_lookupResult . symbol = ( _lineEnd > _lineStart ) ? ( _lineEnd - 1 ) : _lineStart ;
_lookupResult . afterSymbol = ( _lineEnd > _lineStart ) ? true : false ;
// _lookupResult.uponSymbol = ((_lookupX >= _x) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockTSkip)) ? true : false;
} else {
_lookupResult . symbol = _lineStart ;
_lookupResult . afterSymbol = false ;
// _lookupResult.uponSymbol = ((_lookupX >= _x) && (_lineStart > 0)) ? true : false;
}
}
if ( _lookupLink ) {
_lookupResult . link . clear ( ) ;
2014-05-30 12:53:19 +04:00
}
2016-04-13 21:29:32 +03:00
_lookupResult . uponSymbol = false ;
2016-01-07 17:49:54 +08:00
return false ;
2016-04-13 21:29:32 +03:00
} else if ( _lookupX > = x + ( _w - _wLeft ) ) {
2014-05-30 12:53:19 +04:00
if ( _parDirection = = Qt : : RightToLeft ) {
2016-04-13 21:29:32 +03:00
_lookupResult . symbol = _lineStart ;
_lookupResult . afterSymbol = false ;
// _lookupResult.uponSymbol = ((_lookupX < _x + _w) && (_lineStart > 0)) ? true : false;
2014-05-30 12:53:19 +04:00
} else {
2016-04-13 21:29:32 +03:00
_lookupResult . symbol = ( _lineEnd > _lineStart ) ? ( _lineEnd - 1 ) : _lineStart ;
_lookupResult . afterSymbol = ( _lineEnd > _lineStart ) ? true : false ;
// _lookupResult.uponSymbol = ((_lookupX < _x + _w) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockTSkip)) ? true : false;
}
if ( _lookupLink ) {
_lookupResult . link . clear ( ) ;
2014-05-30 12:53:19 +04:00
}
2016-04-13 21:29:32 +03:00
_lookupResult . uponSymbol = false ;
2014-05-30 12:53:19 +04:00
return false ;
}
}
2016-06-09 14:51:24 +03:00
if ( _fullWidthSelection ) {
bool selectFromStart = ( _selection . to > _lineStart ) & & ( _lineStart > 0 ) & & ( _selection . from < = _lineStart ) ;
bool selectTillEnd = ( _selection . to > = _lineEnd ) & & ( _lineEnd < _t - > _text . size ( ) ) & & ( _selection . from < _lineEnd ) & & ( ! _endBlock | | _endBlock - > type ( ) ! = TextBlockTSkip ) ;
2014-05-30 12:53:19 +04:00
2016-06-09 14:51:24 +03:00
if ( ( selectFromStart & & _parDirection = = Qt : : LeftToRight ) | | ( selectTillEnd & & _parDirection = = Qt : : RightToLeft ) ) {
if ( x > _x ) {
2017-02-16 17:07:16 +03:00
fillSelectRange ( _x , x ) ;
2016-06-09 14:51:24 +03:00
}
2014-05-30 12:53:19 +04:00
}
2016-06-09 14:51:24 +03:00
if ( ( selectTillEnd & & _parDirection = = Qt : : LeftToRight ) | | ( selectFromStart & & _parDirection = = Qt : : RightToLeft ) ) {
if ( x < _x + _wLeft ) {
2017-02-16 17:07:16 +03:00
fillSelectRange ( x + _w - _wLeft , _x + _w ) ;
2016-06-09 14:51:24 +03:00
}
2014-05-30 12:53:19 +04:00
}
}
2015-06-15 20:19:24 +03:00
if ( trimmedLineEnd = = _lineStart & & ! elidedLine ) return true ;
2014-05-30 12:53:19 +04:00
2015-06-15 20:19:24 +03:00
if ( ! elidedLine ) initParagraphBidi ( ) ; // if was not inited
2014-05-30 12:53:19 +04:00
2016-12-23 16:21:01 +03:00
_f = _t - > _st - > font ;
2014-05-30 12:53:19 +04:00
QStackTextEngine engine ( lineText , _f - > f ) ;
engine . option . setTextDirection ( _parDirection ) ;
_e = & engine ;
eItemize ( ) ;
QScriptLine line ;
line . from = lineStart ;
line . length = lineLength ;
eShapeLine ( line ) ;
int firstItem = engine . findItem ( line . from ) , lastItem = engine . findItem ( line . from + line . length - 1 ) ;
int nItems = ( firstItem > = 0 & & lastItem > = firstItem ) ? ( lastItem - firstItem + 1 ) : 0 ;
if ( ! nItems ) {
return true ;
}
2015-12-11 21:11:38 +03:00
int skipIndex = - 1 ;
2014-05-30 12:53:19 +04:00
QVarLengthArray < int > visualOrder ( nItems ) ;
QVarLengthArray < uchar > levels ( nItems ) ;
for ( int i = 0 ; i < nItems ; + + i ) {
2017-07-06 18:58:05 +03:00
auto & si = engine . layoutData - > items [ firstItem + i ] ;
2014-05-30 12:53:19 +04:00
while ( nextBlock & & nextBlock - > from ( ) < = _localFrom + si . position ) {
currentBlock = nextBlock ;
2017-07-06 18:58:05 +03:00
nextBlock = ( + + blockIndex < _blocksSize ) ? _t - > _blocks [ blockIndex ] . get ( ) : nullptr ;
2014-05-30 12:53:19 +04:00
}
2017-07-06 18:58:05 +03:00
auto _type = currentBlock - > type ( ) ;
2015-10-23 18:06:56 +02:00
if ( _type = = TextBlockTSkip ) {
2014-05-30 12:53:19 +04:00
levels [ i ] = si . analysis . bidiLevel = 0 ;
2015-12-11 21:11:38 +03:00
skipIndex = i ;
2014-05-30 12:53:19 +04:00
} else {
levels [ i ] = si . analysis . bidiLevel ;
}
if ( si . analysis . flags = = QScriptAnalysis : : Object ) {
2015-10-23 18:06:56 +02:00
if ( _type = = TextBlockTEmoji | | _type = = TextBlockTSkip ) {
2015-06-15 20:19:24 +03:00
si . width = currentBlock - > f_width ( ) + ( nextBlock = = _endBlock & & ( ! nextBlock | | nextBlock - > from ( ) > = trimmedLineEnd ) ? 0 : currentBlock - > f_rpadding ( ) ) ;
2014-05-30 12:53:19 +04:00
}
}
}
QTextEngine : : bidiReorder ( nItems , levels . data ( ) , visualOrder . data ( ) ) ;
2015-12-11 21:11:38 +03:00
if ( rtl ( ) & & skipIndex = = nItems - 1 ) {
for ( int32 i = nItems ; i > 1 ; ) {
- - i ;
visualOrder [ i ] = visualOrder [ i - 1 ] ;
}
visualOrder [ 0 ] = skipIndex ;
}
2014-05-30 12:53:19 +04:00
blockIndex = _lineStartBlock ;
2017-07-06 18:58:05 +03:00
currentBlock = _t - > _blocks [ blockIndex ] . get ( ) ;
nextBlock = ( + + blockIndex < _blocksSize ) ? _t - > _blocks [ blockIndex ] . get ( ) : nullptr ;
2014-05-30 12:53:19 +04:00
2016-12-23 16:21:01 +03:00
int32 textY = _y + _yDelta + _t - > _st - > font - > ascent , emojiY = ( _t - > _st - > font - > height - st : : emojiSize ) / 2 ;
2014-05-30 12:53:19 +04:00
2017-02-09 16:46:14 +03:00
applyBlockProperties ( currentBlock ) ;
2014-05-30 12:53:19 +04:00
for ( int i = 0 ; i < nItems ; + + i ) {
int item = firstItem + visualOrder [ i ] ;
const QScriptItem & si = engine . layoutData - > items . at ( item ) ;
bool rtl = ( si . analysis . bidiLevel % 2 ) ;
while ( blockIndex > _lineStartBlock + 1 & & _t - > _blocks [ blockIndex - 1 ] - > from ( ) > _localFrom + si . position ) {
nextBlock = currentBlock ;
2017-07-06 18:58:05 +03:00
currentBlock = _t - > _blocks [ - - blockIndex - 1 ] . get ( ) ;
2017-02-09 16:46:14 +03:00
applyBlockProperties ( currentBlock ) ;
2014-05-30 12:53:19 +04:00
}
while ( nextBlock & & nextBlock - > from ( ) < = _localFrom + si . position ) {
currentBlock = nextBlock ;
2017-07-06 18:58:05 +03:00
nextBlock = ( + + blockIndex < _blocksSize ) ? _t - > _blocks [ blockIndex ] . get ( ) : nullptr ;
2017-02-09 16:46:14 +03:00
applyBlockProperties ( currentBlock ) ;
2014-05-30 12:53:19 +04:00
}
if ( si . analysis . flags > = QScriptAnalysis : : TabOrObject ) {
TextBlockType _type = currentBlock - > type ( ) ;
2016-04-13 21:29:32 +03:00
if ( ! _p & & _lookupX > = x & & _lookupX < x + si . width ) { // _lookupRequest
if ( _lookupLink ) {
if ( currentBlock - > lnkIndex ( ) & & _lookupY > = _y + _yDelta & & _lookupY < _y + _yDelta + _fontHeight ) {
_lookupResult . link = _t - > _links . at ( currentBlock - > lnkIndex ( ) - 1 ) ;
}
2014-05-30 12:53:19 +04:00
}
2016-04-13 21:29:32 +03:00
if ( _type ! = TextBlockTSkip ) {
_lookupResult . uponSymbol = true ;
2014-05-30 12:53:19 +04:00
}
2016-04-13 21:29:32 +03:00
if ( _lookupSymbol ) {
if ( _type = = TextBlockTSkip ) {
if ( _parDirection = = Qt : : RightToLeft ) {
_lookupResult . symbol = _lineStart ;
_lookupResult . afterSymbol = false ;
} else {
_lookupResult . symbol = ( trimmedLineEnd > _lineStart ) ? ( trimmedLineEnd - 1 ) : _lineStart ;
_lookupResult . afterSymbol = ( trimmedLineEnd > _lineStart ) ? true : false ;
}
return false ;
2014-05-30 12:53:19 +04:00
}
2017-02-16 17:07:16 +03:00
// Emoji with spaces after symbol lookup
auto chFrom = _str + currentBlock - > from ( ) ;
auto chTo = chFrom + ( ( nextBlock ? nextBlock - > from ( ) : _t - > _text . size ( ) ) - currentBlock - > from ( ) ) ;
auto spacesWidth = ( si . width - currentBlock - > f_width ( ) ) ;
auto spacesCount = 0 ;
while ( chTo > chFrom & & ( chTo - 1 ) - > unicode ( ) = = QChar : : Space ) {
+ + spacesCount ;
- - chTo ;
}
if ( spacesCount > 0 ) { // Check if we're over a space.
2016-04-13 21:29:32 +03:00
if ( rtl ) {
2017-02-16 17:07:16 +03:00
if ( _lookupX < x + spacesWidth ) {
_lookupResult . symbol = ( chTo - _str ) ; // up to a space, included, rtl
_lookupResult . afterSymbol = ( _lookupX < x + ( spacesWidth / 2 ) ) ? true : false ;
2016-04-13 21:29:32 +03:00
return false ;
}
2017-02-16 17:07:16 +03:00
} else if ( _lookupX > = x + si . width - spacesWidth ) {
_lookupResult . symbol = ( chTo - _str ) ; // up to a space, inclided, ltr
_lookupResult . afterSymbol = ( _lookupX > = x + si . width - spacesWidth + ( spacesWidth / 2 ) ) ? true : false ;
2014-05-30 12:53:19 +04:00
return false ;
}
2016-04-13 21:29:32 +03:00
}
if ( _lookupX < x + ( rtl ? ( si . width - currentBlock - > f_width ( ) ) : 0 ) + ( currentBlock - > f_width ( ) / 2 ) ) {
_lookupResult . symbol = ( ( rtl & & chTo > chFrom ) ? ( chTo - 1 ) : chFrom ) - _str ;
_lookupResult . afterSymbol = ( rtl & & chTo > chFrom ) ? true : false ;
} else {
_lookupResult . symbol = ( ( rtl | | chTo < = chFrom ) ? chFrom : ( chTo - 1 ) ) - _str ;
_lookupResult . afterSymbol = ( rtl | | chTo < = chFrom ) ? false : true ;
2014-05-30 12:53:19 +04:00
}
}
return false ;
2015-10-23 18:06:56 +02:00
} else if ( _p & & _type = = TextBlockTEmoji ) {
2017-02-16 17:07:16 +03:00
auto glyphX = x ;
auto spacesWidth = ( si . width - currentBlock - > f_width ( ) ) ;
2014-05-30 12:53:19 +04:00
if ( rtl ) {
2017-02-16 17:07:16 +03:00
glyphX + = spacesWidth ;
2014-05-30 12:53:19 +04:00
}
2016-04-13 21:29:32 +03:00
if ( _localFrom + si . position < _selection . to ) {
2017-02-16 17:07:16 +03:00
auto chFrom = _str + currentBlock - > from ( ) ;
auto chTo = chFrom + ( ( nextBlock ? nextBlock - > from ( ) : _t - > _text . size ( ) ) - currentBlock - > from ( ) ) ;
2016-04-13 21:29:32 +03:00
if ( _localFrom + si . position > = _selection . from ) { // could be without space
if ( chTo = = chFrom | | ( chTo - 1 ) - > unicode ( ) ! = QChar : : Space | | _selection . to > = ( chTo - _str ) ) {
2017-02-16 17:07:16 +03:00
fillSelectRange ( x , x + si . width ) ;
2014-05-30 12:53:19 +04:00
} else { // or with space
2017-02-16 17:07:16 +03:00
fillSelectRange ( glyphX , glyphX + currentBlock - > f_width ( ) ) ;
2014-05-30 12:53:19 +04:00
}
2016-04-13 21:29:32 +03:00
} else if ( chTo > chFrom & & ( chTo - 1 ) - > unicode ( ) = = QChar : : Space & & ( chTo - 1 - _str ) > = _selection . from ) {
2014-05-30 12:53:19 +04:00
if ( rtl ) { // rtl space only
2017-02-16 17:07:16 +03:00
fillSelectRange ( x , glyphX ) ;
2014-05-30 12:53:19 +04:00
} else { // ltr space only
2017-02-16 17:07:16 +03:00
fillSelectRange ( x + currentBlock - > f_width ( ) , x + si . width ) ;
2014-05-30 12:53:19 +04:00
}
}
}
2017-02-16 17:07:16 +03:00
emojiDraw ( * _p , static_cast < EmojiBlock * > ( currentBlock ) - > emoji , ( glyphX + st : : emojiPadding ) . toInt ( ) , _y + _yDelta + emojiY ) ;
2014-05-30 12:53:19 +04:00
// } else if (_p && currentBlock->type() == TextBlockSkip) { // debug
// _p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast<SkipBlock*>(currentBlock)->height()), QColor(0, 0, 0, 32));
}
x + = si . width ;
continue ;
}
unsigned short * logClusters = engine . logClusters ( & si ) ;
QGlyphLayout glyphs = engine . shapedGlyphs ( & si ) ;
int itemStart = qMax ( line . from , si . position ) , itemEnd ;
int itemLength = engine . length ( item ) ;
int glyphsStart = logClusters [ itemStart - si . position ] , glyphsEnd ;
if ( line . from + line . length < si . position + itemLength ) {
itemEnd = line . from + line . length ;
glyphsEnd = logClusters [ itemEnd - si . position ] ;
} else {
itemEnd = si . position + itemLength ;
glyphsEnd = si . num_glyphs ;
}
QFixed itemWidth = 0 ;
for ( int g = glyphsStart ; g < glyphsEnd ; + + g )
itemWidth + = glyphs . effectiveAdvance ( g ) ;
2016-04-13 21:29:32 +03:00
if ( ! _p & & _lookupX > = x & & _lookupX < x + itemWidth ) { // _lookupRequest
if ( _lookupLink ) {
if ( currentBlock - > lnkIndex ( ) & & _lookupY > = _y + _yDelta & & _lookupY < _y + _yDelta + _fontHeight ) {
_lookupResult . link = _t - > _links . at ( currentBlock - > lnkIndex ( ) - 1 ) ;
2014-05-30 12:53:19 +04:00
}
2016-04-13 21:29:32 +03:00
}
_lookupResult . uponSymbol = true ;
if ( _lookupSymbol ) {
QFixed tmpx = rtl ? ( x + itemWidth ) : x ;
for ( int ch = 0 , g , itemL = itemEnd - itemStart ; ch < itemL ; ) {
g = logClusters [ itemStart - si . position + ch ] ;
QFixed gwidth = glyphs . effectiveAdvance ( g ) ;
// ch2 - glyph end, ch - glyph start, (ch2 - ch) - how much chars it takes
int ch2 = ch + 1 ;
while ( ( ch2 < itemL ) & & ( g = = logClusters [ itemStart - si . position + ch2 ] ) ) {
+ + ch2 ;
}
for ( int charsCount = ( ch2 - ch ) ; ch < ch2 ; + + ch ) {
QFixed shift1 = QFixed ( 2 * ( charsCount - ( ch2 - ch ) ) + 2 ) * gwidth / QFixed ( 2 * charsCount ) ,
shift2 = QFixed ( 2 * ( charsCount - ( ch2 - ch ) ) + 1 ) * gwidth / QFixed ( 2 * charsCount ) ;
if ( ( rtl & & _lookupX > = tmpx - shift1 ) | |
( ! rtl & & _lookupX < tmpx + shift1 ) ) {
_lookupResult . symbol = _localFrom + itemStart + ch ;
if ( ( rtl & & _lookupX > = tmpx - shift2 ) | |
( ! rtl & & _lookupX < tmpx + shift2 ) ) {
_lookupResult . afterSymbol = false ;
} else {
_lookupResult . afterSymbol = true ;
}
return false ;
2014-05-30 12:53:19 +04:00
}
2016-04-13 21:29:32 +03:00
}
if ( rtl ) {
tmpx - = gwidth ;
} else {
tmpx + = gwidth ;
2014-05-30 12:53:19 +04:00
}
}
2016-04-13 21:29:32 +03:00
if ( itemEnd > itemStart ) {
_lookupResult . symbol = _localFrom + itemEnd - 1 ;
_lookupResult . afterSymbol = true ;
2014-05-30 12:53:19 +04:00
} else {
2016-04-13 21:29:32 +03:00
_lookupResult . symbol = _localFrom + itemStart ;
_lookupResult . afterSymbol = false ;
2014-05-30 12:53:19 +04:00
}
}
return false ;
} else if ( _p ) {
QTextCharFormat format ;
QTextItemInt gf ( glyphs . mid ( glyphsStart , glyphsEnd - glyphsStart ) ,
& _e - > fnt , engine . layoutData - > string . unicode ( ) + itemStart ,
itemEnd - itemStart , engine . fontEngine ( si ) , format ) ;
gf . logClusters = logClusters + itemStart - si . position ;
gf . width = itemWidth ;
gf . justified = false ;
gf . initWithScriptItem ( si ) ;
2017-02-09 16:46:14 +03:00
auto hasSelected = false ;
auto hasNotSelected = true ;
auto selectedRect = QRect ( ) ;
2016-04-13 21:29:32 +03:00
if ( _localFrom + itemStart < _selection . to & & _localFrom + itemEnd > _selection . from ) {
2017-02-09 16:46:14 +03:00
hasSelected = true ;
auto selX = x ;
auto selWidth = itemWidth ;
if ( _localFrom + itemStart > = _selection . from & & _localFrom + itemEnd < = _selection . to ) {
hasNotSelected = false ;
} else {
2014-05-30 12:53:19 +04:00
selWidth = 0 ;
int itemL = itemEnd - itemStart ;
2016-04-13 21:29:32 +03:00
int selStart = _selection . from - ( _localFrom + itemStart ) , selEnd = _selection . to - ( _localFrom + itemStart ) ;
2014-05-30 12:53:19 +04:00
if ( selStart < 0 ) selStart = 0 ;
if ( selEnd > itemL ) selEnd = itemL ;
for ( int ch = 0 , g ; ch < selEnd ; ) {
g = logClusters [ itemStart - si . position + ch ] ;
QFixed gwidth = glyphs . effectiveAdvance ( g ) ;
// ch2 - glyph end, ch - glyph start, (ch2 - ch) - how much chars it takes
int ch2 = ch + 1 ;
while ( ( ch2 < itemL ) & & ( g = = logClusters [ itemStart - si . position + ch2 ] ) ) {
+ + ch2 ;
}
if ( ch2 < = selStart ) {
selX + = gwidth ;
} else if ( ch > = selStart & & ch2 < = selEnd ) {
selWidth + = gwidth ;
} else {
int sStart = ch , sEnd = ch2 ;
if ( ch < selStart ) {
sStart = selStart ;
selX + = QFixed ( sStart - ch ) * gwidth / QFixed ( ch2 - ch ) ;
}
if ( ch2 > = selEnd ) {
sEnd = selEnd ;
selWidth + = QFixed ( sEnd - sStart ) * gwidth / QFixed ( ch2 - ch ) ;
break ;
}
selWidth + = QFixed ( sEnd - sStart ) * gwidth / QFixed ( ch2 - ch ) ;
}
ch = ch2 ;
}
}
if ( rtl ) selX = x + itemWidth - ( selX - x ) - selWidth ;
2017-02-16 17:07:16 +03:00
selectedRect = QRect ( selX . toInt ( ) , _y + _yDelta , ( selX + selWidth ) . toInt ( ) - selX . toInt ( ) , _fontHeight ) ;
fillSelectRange ( selX , selX + selWidth ) ;
2017-02-09 16:46:14 +03:00
}
if ( Q_UNLIKELY ( hasSelected ) ) {
if ( Q_UNLIKELY ( hasNotSelected ) ) {
auto clippingEnabled = _p - > hasClipping ( ) ;
auto clippingRegion = _p - > clipRegion ( ) ;
_p - > setClipRect ( selectedRect , Qt : : IntersectClip ) ;
_p - > setPen ( * _currentPenSelected ) ;
_p - > drawTextItem ( QPointF ( x . toReal ( ) , textY ) , gf ) ;
2017-07-26 15:24:18 +03:00
auto externalClipping = clippingEnabled ? clippingRegion : QRegion ( QRect ( ( _x - _w ) . toInt ( ) , _y - _lineHeight , ( _x + 2 * _w ) . toInt ( ) , _y + 2 * _lineHeight ) ) ;
_p - > setClipRegion ( externalClipping - selectedRect ) ;
2017-02-09 16:46:14 +03:00
_p - > setPen ( * _currentPen ) ;
_p - > drawTextItem ( QPointF ( x . toReal ( ) , textY ) , gf ) ;
if ( clippingEnabled ) {
_p - > setClipRegion ( clippingRegion ) ;
} else {
_p - > setClipping ( false ) ;
}
} else {
_p - > setPen ( * _currentPenSelected ) ;
_p - > drawTextItem ( QPointF ( x . toReal ( ) , textY ) , gf ) ;
}
} else {
_p - > setPen ( * _currentPen ) ;
_p - > drawTextItem ( QPointF ( x . toReal ( ) , textY ) , gf ) ;
2014-05-30 12:53:19 +04:00
}
}
x + = itemWidth ;
}
return true ;
}
2017-02-16 17:07:16 +03:00
void fillSelectRange ( QFixed from , QFixed to ) {
auto left = from . toInt ( ) ;
auto width = to . toInt ( ) - left ;
_p - > fillRect ( left , _y + _yDelta , width , _fontHeight , _textPalette - > selectBg ) ;
}
2014-05-30 12:53:19 +04:00
void elideSaveBlock ( int32 blockIndex , ITextBlock * & _endBlock , int32 elideStart , int32 elideWidth ) {
2016-06-08 19:40:33 +03:00
if ( _elideSavedBlock ) {
restoreAfterElided ( ) ;
}
2014-05-30 12:53:19 +04:00
_elideSavedIndex = blockIndex ;
2017-07-06 18:58:05 +03:00
auto mutableText = const_cast < Text * > ( _t ) ;
_elideSavedBlock = std : : move ( mutableText - > _blocks [ blockIndex ] ) ;
mutableText - > _blocks [ blockIndex ] = std : : make_unique < TextBlock > ( _t - > _st - > font , _t - > _text , QFIXED_MAX , elideStart , 0 , _elideSavedBlock - > flags ( ) , _elideSavedBlock - > lnkIndex ( ) ) ;
2014-05-30 12:53:19 +04:00
_blocksSize = blockIndex + 1 ;
2017-07-06 18:58:05 +03:00
_endBlock = ( blockIndex + 1 < _t - > _blocks . size ( ) ? _t - > _blocks [ blockIndex + 1 ] . get ( ) : nullptr ) ;
2014-05-30 12:53:19 +04:00
}
void setElideBidi ( int32 elideStart , int32 elideLen ) {
int32 newParLength = elideStart + elideLen - _parStart ;
if ( newParLength > _parAnalysis . size ( ) ) {
_parAnalysis . resize ( newParLength ) ;
}
for ( int32 i = elideLen ; i > 0 ; - - i ) {
_parAnalysis [ newParLength - i ] . bidiLevel = ( _parDirection = = Qt : : RightToLeft ) ? 1 : 0 ;
}
}
void prepareElidedLine ( QString & lineText , int32 lineStart , int32 & lineLength , ITextBlock * & _endBlock , int repeat = 0 ) {
static const QString _Elide = qsl ( " ... " ) ;
2016-12-23 16:21:01 +03:00
_f = _t - > _st - > font ;
2014-05-30 12:53:19 +04:00
QStackTextEngine engine ( lineText , _f - > f ) ;
engine . option . setTextDirection ( _parDirection ) ;
_e = & engine ;
eItemize ( ) ;
2017-07-06 18:58:05 +03:00
auto blockIndex = _lineStartBlock ;
auto currentBlock = _t - > _blocks [ blockIndex ] . get ( ) ;
auto nextBlock = ( + + blockIndex < _blocksSize ) ? _t - > _blocks [ blockIndex ] . get ( ) : nullptr ;
2014-05-30 12:53:19 +04:00
QScriptLine line ;
line . from = lineStart ;
line . length = lineLength ;
eShapeLine ( line ) ;
2017-01-30 18:27:13 +03:00
auto elideWidth = _f - > elidew ;
2015-04-04 23:01:34 +03:00
_wLeft = _w - elideWidth - _elideRemoveFromEnd ;
2014-05-30 12:53:19 +04:00
int firstItem = engine . findItem ( line . from ) , lastItem = engine . findItem ( line . from + line . length - 1 ) ;
int nItems = ( firstItem > = 0 & & lastItem > = firstItem ) ? ( lastItem - firstItem + 1 ) : 0 , i ;
for ( i = 0 ; i < nItems ; + + i ) {
QScriptItem & si ( engine . layoutData - > items [ firstItem + i ] ) ;
while ( nextBlock & & nextBlock - > from ( ) < = _localFrom + si . position ) {
currentBlock = nextBlock ;
2017-07-06 18:58:05 +03:00
nextBlock = ( + + blockIndex < _blocksSize ) ? _t - > _blocks [ blockIndex ] . get ( ) : nullptr ;
2014-05-30 12:53:19 +04:00
}
TextBlockType _type = currentBlock - > type ( ) ;
if ( si . analysis . flags = = QScriptAnalysis : : Object ) {
2015-10-23 18:06:56 +02:00
if ( _type = = TextBlockTEmoji | | _type = = TextBlockTSkip ) {
2014-05-30 12:53:19 +04:00
si . width = currentBlock - > f_width ( ) + currentBlock - > f_rpadding ( ) ;
}
}
2015-10-23 18:06:56 +02:00
if ( _type = = TextBlockTEmoji | | _type = = TextBlockTSkip | | _type = = TextBlockTNewline ) {
2014-05-30 12:53:19 +04:00
if ( _wLeft < si . width ) {
lineText = lineText . mid ( 0 , currentBlock - > from ( ) - _localFrom ) + _Elide ;
lineLength = currentBlock - > from ( ) + _Elide . size ( ) - _lineStart ;
2016-04-19 16:52:51 +03:00
_selection . to = qMin ( _selection . to , currentBlock - > from ( ) ) ;
2014-05-30 12:53:19 +04:00
setElideBidi ( currentBlock - > from ( ) , _Elide . size ( ) ) ;
elideSaveBlock ( blockIndex - 1 , _endBlock , currentBlock - > from ( ) , elideWidth ) ;
return ;
}
_wLeft - = si . width ;
2015-10-23 18:06:56 +02:00
} else if ( _type = = TextBlockTText ) {
2014-05-30 12:53:19 +04:00
unsigned short * logClusters = engine . logClusters ( & si ) ;
QGlyphLayout glyphs = engine . shapedGlyphs ( & si ) ;
int itemStart = qMax ( line . from , si . position ) , itemEnd ;
int itemLength = engine . length ( firstItem + i ) ;
int glyphsStart = logClusters [ itemStart - si . position ] , glyphsEnd ;
if ( line . from + line . length < si . position + itemLength ) {
itemEnd = line . from + line . length ;
glyphsEnd = logClusters [ itemEnd - si . position ] ;
} else {
itemEnd = si . position + itemLength ;
glyphsEnd = si . num_glyphs ;
}
2017-02-26 19:20:17 +03:00
for ( auto g = glyphsStart ; g < glyphsEnd ; + + g ) {
auto adv = glyphs . effectiveAdvance ( g ) ;
2014-05-30 12:53:19 +04:00
if ( _wLeft < adv ) {
2017-02-26 19:20:17 +03:00
auto pos = itemStart ;
2014-05-30 12:53:19 +04:00
while ( pos < itemEnd & & logClusters [ pos - si . position ] < g ) {
+ + pos ;
}
if ( lineText . size ( ) < = pos | | repeat > 3 ) {
lineText + = _Elide ;
lineLength = _localFrom + pos + _Elide . size ( ) - _lineStart ;
2016-04-15 12:59:02 +03:00
_selection . to = qMin ( _selection . to , uint16 ( _localFrom + pos ) ) ;
2014-05-30 12:53:19 +04:00
setElideBidi ( _localFrom + pos , _Elide . size ( ) ) ;
_blocksSize = blockIndex ;
_endBlock = nextBlock ;
} else {
lineText = lineText . mid ( 0 , pos ) ;
lineLength = _localFrom + pos - _lineStart ;
_blocksSize = blockIndex ;
_endBlock = nextBlock ;
prepareElidedLine ( lineText , lineStart , lineLength , _endBlock , repeat + 1 ) ;
}
return ;
} else {
_wLeft - = adv ;
}
}
}
}
2016-04-13 21:29:32 +03:00
int32 elideStart = _localFrom + lineText . size ( ) ;
2016-04-15 12:59:02 +03:00
_selection . to = qMin ( _selection . to , uint16 ( elideStart ) ) ;
2014-05-30 12:53:19 +04:00
setElideBidi ( elideStart , _Elide . size ( ) ) ;
lineText + = _Elide ;
lineLength + = _Elide . size ( ) ;
if ( ! repeat ) {
2017-07-06 18:58:05 +03:00
for ( ; blockIndex < _blocksSize & & _t - > _blocks [ blockIndex ] . get ( ) ! = _endBlock & & _t - > _blocks [ blockIndex ] - > from ( ) < elideStart ; + + blockIndex ) {
2014-06-12 15:20:14 +04:00
}
if ( blockIndex < _blocksSize ) {
elideSaveBlock ( blockIndex , _endBlock , elideStart , elideWidth ) ;
2014-05-30 12:53:19 +04:00
}
}
}
void restoreAfterElided ( ) {
if ( _elideSavedBlock ) {
2017-07-06 18:58:05 +03:00
const_cast < Text * > ( _t ) - > _blocks [ _elideSavedIndex ] = std : : move ( _elideSavedBlock ) ;
2014-05-30 12:53:19 +04:00
}
}
// COPIED FROM qtextengine.cpp AND MODIFIED
void eShapeLine ( const QScriptLine & line ) {
2017-01-30 18:27:13 +03:00
int item = _e - > findItem ( line . from ) ;
2014-05-30 12:53:19 +04:00
if ( item = = - 1 )
return ;
2017-01-30 18:27:13 +03:00
# ifdef OS_MAC_OLD
auto end = _e - > findItem ( line . from + line . length - 1 ) ;
# else // OS_MAC_OLD
auto end = _e - > findItem ( line . from + line . length - 1 , item ) ;
# endif // OS_MAC_OLD
2017-07-06 18:58:05 +03:00
auto blockIndex = _lineStartBlock ;
auto currentBlock = _t - > _blocks [ blockIndex ] . get ( ) ;
auto nextBlock = ( + + blockIndex < _blocksSize ) ? _t - > _blocks [ blockIndex ] . get ( ) : nullptr ;
2014-05-30 12:53:19 +04:00
eSetFont ( currentBlock ) ;
2017-01-30 18:27:13 +03:00
for ( ; item < = end ; + + item ) {
2014-05-30 12:53:19 +04:00
QScriptItem & si = _e - > layoutData - > items [ item ] ;
while ( nextBlock & & nextBlock - > from ( ) < = _localFrom + si . position ) {
currentBlock = nextBlock ;
2017-07-06 18:58:05 +03:00
nextBlock = ( + + blockIndex < _blocksSize ) ? _t - > _blocks [ blockIndex ] . get ( ) : nullptr ;
2014-05-30 12:53:19 +04:00
eSetFont ( currentBlock ) ;
}
_e - > shape ( item ) ;
}
}
2015-07-03 18:55:22 +03:00
style : : font applyFlags ( int32 flags , const style : : font & f ) {
style : : font result = f ;
2015-10-23 18:06:56 +02:00
if ( ( flags & TextBlockFPre ) | | ( flags & TextBlockFCode ) ) {
result = App : : monofont ( ) ;
if ( result - > size ( ) ! = f - > size ( ) | | result - > flags ( ) ! = f - > flags ( ) ) {
result = style : : font ( f - > size ( ) , f - > flags ( ) , result - > family ( ) ) ;
}
} else {
if ( flags & TextBlockFBold ) {
result = result - > bold ( ) ;
} else if ( flags & TextBlockFSemibold ) {
result = st : : semiboldFont ;
if ( result - > size ( ) ! = f - > size ( ) | | result - > flags ( ) ! = f - > flags ( ) ) {
result = style : : font ( f - > size ( ) , f - > flags ( ) , result - > family ( ) ) ;
}
}
if ( flags & TextBlockFItalic ) result = result - > italic ( ) ;
if ( flags & TextBlockFUnderline ) result = result - > underline ( ) ;
2015-11-04 18:46:01 -05:00
if ( flags & TextBlockFTilde ) { // tilde fix in OpenSans
2015-10-27 23:43:42 -04:00
result = st : : semiboldFont ;
2015-10-23 18:06:56 +02:00
}
}
2015-07-03 18:55:22 +03:00
return result ;
}
2014-05-30 12:53:19 +04:00
void eSetFont ( ITextBlock * block ) {
2016-12-23 16:21:01 +03:00
style : : font newFont = _t - > _st - > font ;
2014-05-30 12:53:19 +04:00
int flags = block - > flags ( ) ;
2015-07-03 19:19:46 +03:00
if ( flags ) {
2016-12-23 16:21:01 +03:00
newFont = applyFlags ( flags , _t - > _st - > font ) ;
2015-07-03 19:19:46 +03:00
}
2015-07-03 11:47:16 +03:00
if ( block - > lnkIndex ( ) ) {
2016-03-29 20:17:00 +03:00
if ( ClickHandler : : showAsActive ( _t - > _links . at ( block - > lnkIndex ( ) - 1 ) ) ) {
2016-12-23 16:21:01 +03:00
if ( _t - > _st - > font ! = _t - > _st - > linkFontOver ) {
newFont = _t - > _st - > linkFontOver ;
2014-05-30 12:53:19 +04:00
}
} else {
2016-12-23 16:21:01 +03:00
if ( _t - > _st - > font ! = _t - > _st - > linkFont ) {
newFont = _t - > _st - > linkFont ;
2016-03-29 20:17:00 +03:00
}
2014-05-30 12:53:19 +04:00
}
}
if ( newFont ! = _f ) {
2016-12-23 16:21:01 +03:00
if ( newFont - > family ( ) = = _t - > _st - > font - > family ( ) ) {
newFont = applyFlags ( flags | newFont - > flags ( ) , _t - > _st - > font ) ;
2015-07-03 18:55:22 +03:00
}
2014-05-30 12:53:19 +04:00
_f = newFont ;
_e - > fnt = _f - > f ;
_e - > resetFontEngineCache ( ) ;
}
}
void eItemize ( ) {
_e - > validate ( ) ;
if ( _e - > layoutData - > items . size ( ) )
return ;
int length = _e - > layoutData - > string . length ( ) ;
if ( ! length )
return ;
const ushort * string = reinterpret_cast < const ushort * > ( _e - > layoutData - > string . unicode ( ) ) ;
2017-07-06 18:58:05 +03:00
auto blockIndex = _lineStartBlock ;
auto currentBlock = _t - > _blocks [ blockIndex ] . get ( ) ;
auto nextBlock = ( + + blockIndex < _blocksSize ) ? _t - > _blocks [ blockIndex ] . get ( ) : nullptr ;
2014-05-30 12:53:19 +04:00
_e - > layoutData - > hasBidi = _parHasBidi ;
2017-07-06 18:58:05 +03:00
auto analysis = _parAnalysis . data ( ) + ( _localFrom - _parStart ) ;
2014-05-30 12:53:19 +04:00
{
QVarLengthArray < uchar > scripts ( length ) ;
QUnicodeTools : : initScripts ( string , length , scripts . data ( ) ) ;
for ( int i = 0 ; i < length ; + + i )
analysis [ i ] . script = scripts . at ( i ) ;
}
blockIndex = _lineStartBlock ;
2017-07-06 18:58:05 +03:00
currentBlock = _t - > _blocks [ blockIndex ] . get ( ) ;
nextBlock = ( + + blockIndex < _blocksSize ) ? _t - > _blocks [ blockIndex ] . get ( ) : nullptr ;
2014-05-30 12:53:19 +04:00
2017-07-06 18:58:05 +03:00
auto start = string ;
auto end = start + length ;
2014-05-30 12:53:19 +04:00
while ( start < end ) {
while ( nextBlock & & nextBlock - > from ( ) < = _localFrom + ( start - string ) ) {
currentBlock = nextBlock ;
2017-07-06 18:58:05 +03:00
nextBlock = ( + + blockIndex < _blocksSize ) ? _t - > _blocks [ blockIndex ] . get ( ) : nullptr ;
2014-05-30 12:53:19 +04:00
}
2017-07-06 18:58:05 +03:00
auto _type = currentBlock - > type ( ) ;
2015-10-23 18:06:56 +02:00
if ( _type = = TextBlockTEmoji | | _type = = TextBlockTSkip ) {
2014-05-30 12:53:19 +04:00
analysis - > script = QChar : : Script_Common ;
analysis - > flags = QScriptAnalysis : : Object ;
} else {
analysis - > flags = QScriptAnalysis : : None ;
}
analysis - > script = hbscript_to_script ( script_to_hbscript ( analysis - > script ) ) ; // retain the old behavior
+ + start ;
+ + analysis ;
}
{
2017-07-06 18:58:05 +03:00
auto i_string = & _e - > layoutData - > string ;
auto i_analysis = _parAnalysis . data ( ) + ( _localFrom - _parStart ) ;
auto i_items = & _e - > layoutData - > items ;
2014-05-30 12:53:19 +04:00
blockIndex = _lineStartBlock ;
2017-07-06 18:58:05 +03:00
currentBlock = _t - > _blocks [ blockIndex ] . get ( ) ;
nextBlock = ( + + blockIndex < _blocksSize ) ? _t - > _blocks [ blockIndex ] . get ( ) : nullptr ;
auto startBlock = currentBlock ;
2014-05-30 12:53:19 +04:00
2017-07-06 18:58:05 +03:00
if ( ! length ) {
2014-05-30 12:53:19 +04:00
return ;
2017-07-06 18:58:05 +03:00
}
auto start = 0 ;
auto end = start + length ;
2014-05-30 12:53:19 +04:00
for ( int i = start + 1 ; i < end ; + + i ) {
while ( nextBlock & & nextBlock - > from ( ) < = _localFrom + i ) {
currentBlock = nextBlock ;
2017-07-06 18:58:05 +03:00
nextBlock = ( + + blockIndex < _blocksSize ) ? _t - > _blocks [ blockIndex ] . get ( ) : nullptr ;
2014-05-30 12:53:19 +04:00
}
// According to the unicode spec we should be treating characters in the Common script
// (punctuation, spaces, etc) as being the same script as the surrounding text for the
// purpose of splitting up text. This is important because, for example, a fullstop
// (0x2E) can be used to indicate an abbreviation and so must be treated as part of a
// word. Thus it must be passed along with the word in languages that have to calculate
// word breaks. For example the thai word "ครม." has no word breaks but the word "ครม"
// does.
// Unfortuntely because we split up the strings for both wordwrapping and for setting
// the font and because Japanese and Chinese are also aliases of the script "Common",
// doing this would break too many things. So instead we only pass the full stop
// along, and nothing else.
if ( currentBlock = = startBlock
& & i_analysis [ i ] . bidiLevel = = i_analysis [ start ] . bidiLevel
& & i_analysis [ i ] . flags = = i_analysis [ start ] . flags
& & ( i_analysis [ i ] . script = = i_analysis [ start ] . script | | i_string - > at ( i ) = = QLatin1Char ( ' . ' ) )
// && i_analysis[i].flags < QScriptAnalysis::SpaceTabOrObject // only emojis are objects here, no tabs
& & i - start < _MaxItemLength )
continue ;
i_items - > append ( QScriptItem ( start , i_analysis [ start ] ) ) ;
start = i ;
startBlock = currentBlock ;
}
i_items - > append ( QScriptItem ( start , i_analysis [ start ] ) ) ;
}
}
QChar : : Direction eSkipBoundryNeutrals ( QScriptAnalysis * analysis ,
const ushort * unicode ,
int & sor , int & eor , BidiControl & control ,
Text : : TextBlocks : : const_iterator i ) {
Text : : TextBlocks : : const_iterator e = _t - > _blocks . cend ( ) , n = i + 1 ;
QChar : : Direction dir = control . basicDirection ( ) ;
int level = sor > 0 ? analysis [ sor - 1 ] . bidiLevel : control . level ;
while ( sor < = _parLength ) {
while ( i ! = _parStartBlock & & ( * i ) - > from ( ) > _parStart + sor ) {
n = i ;
- - i ;
}
while ( n ! = e & & ( * n ) - > from ( ) < = _parStart + sor ) {
i = n ;
+ + n ;
}
TextBlockType _itype = ( * i ) - > type ( ) ;
if ( eor = = _parLength )
dir = control . basicDirection ( ) ;
2015-10-23 18:06:56 +02:00
else if ( _itype = = TextBlockTEmoji )
2014-05-30 12:53:19 +04:00
dir = QChar : : DirCS ;
2015-10-23 18:06:56 +02:00
else if ( _itype = = TextBlockTSkip )
2014-05-30 12:53:19 +04:00
dir = QChar : : DirCS ;
else
dir = QChar : : direction ( unicode [ sor ] ) ;
// Keep skipping DirBN as if it doesn't exist
if ( dir ! = QChar : : DirBN )
break ;
analysis [ sor + + ] . bidiLevel = level ;
}
eor = sor ;
return dir ;
}
// creates the next QScript items.
bool eBidiItemize ( QScriptAnalysis * analysis , BidiControl & control ) {
bool rightToLeft = ( control . basicDirection ( ) = = 1 ) ;
bool hasBidi = rightToLeft ;
int sor = 0 ;
int eor = - 1 ;
const ushort * unicode = reinterpret_cast < const ushort * > ( _t - > _text . unicode ( ) ) + _parStart ;
int current = 0 ;
QChar : : Direction dir = rightToLeft ? QChar : : DirR : QChar : : DirL ;
BidiStatus status ;
Text : : TextBlocks : : const_iterator i = _parStartBlock , e = _t - > _blocks . cend ( ) , n = i + 1 ;
QChar : : Direction sdir ;
TextBlockType _stype = ( * _parStartBlock ) - > type ( ) ;
2015-10-23 18:06:56 +02:00
if ( _stype = = TextBlockTEmoji )
2014-05-30 12:53:19 +04:00
sdir = QChar : : DirCS ;
2015-10-23 18:06:56 +02:00
else if ( _stype = = TextBlockTSkip )
2014-05-30 12:53:19 +04:00
sdir = QChar : : DirCS ;
else
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 < = _parLength ) {
while ( n ! = e & & ( * n ) - > from ( ) < = _parStart + current ) {
i = n ;
+ + n ;
}
QChar : : Direction dirCurrent ;
TextBlockType _itype = ( * i ) - > type ( ) ;
if ( current = = ( int ) _parLength )
dirCurrent = control . basicDirection ( ) ;
2015-10-23 18:06:56 +02:00
else if ( _itype = = TextBlockTEmoji )
2014-05-30 12:53:19 +04:00
dirCurrent = QChar : : DirCS ;
2015-10-23 18:06:56 +02:00
else if ( _itype = = TextBlockTSkip )
2014-05-30 12:53:19 +04:00
dirCurrent = QChar : : DirCS ;
else
dirCurrent = QChar : : direction ( unicode [ current ] ) ;
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 ;
eAppendItems ( 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 ;
eAppendItems ( analysis , sor , eor , control , dir ) ;
dir = control . direction ( ) ;
}
eor = current ;
eAppendItems ( 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 ) {
eAppendItems ( analysis , sor , eor , control , dir ) ;
status . eor = dir = eSkipBoundryNeutrals ( analysis , unicode , sor , eor , control , i ) ;
} 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
eAppendItems ( analysis , sor , eor , control , dir ) ;
status . eor = QChar : : DirON ;
dir = QChar : : DirR ;
}
eor = current - 1 ;
eAppendItems ( analysis , sor , eor , control , dir ) ;
status . eor = dir = eSkipBoundryNeutrals ( analysis , unicode , sor , eor , control , i ) ;
} else {
if ( status . eor ! = QChar : : DirL ) {
eAppendItems ( 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 )
eAppendItems ( 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 ) {
eAppendItems ( analysis , sor , eor , control , dir ) ;
dir = QChar : : DirR ; status . eor = QChar : : DirON ;
eor = current ;
} else {
eor = current - 1 ;
eAppendItems ( 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 ) {
eAppendItems ( analysis , sor , eor , control , dir ) ;
status . eor = QChar : : DirON ;
dir = QChar : : DirAN ;
}
2017-09-28 12:40:26 +03:00
[[fallthrough]] ;
2014-05-30 12:53:19 +04:00
case QChar : : DirEN :
case QChar : : DirL :
eor = current ;
status . eor = dirCurrent ;
break ;
case QChar : : DirR :
case QChar : : DirAL :
case QChar : : DirAN :
if ( eor > = 0 )
eAppendItems ( analysis , sor , eor , control , dir ) ;
else
eor = current ;
status . eor = QChar : : DirEN ;
2017-09-28 12:40:26 +03:00
dir = QChar : : DirAN ;
break ;
2014-05-30 12:53:19 +04:00
case QChar : : DirES :
case QChar : : DirCS :
if ( status . eor = = QChar : : DirEN | | dir = = QChar : : DirAN ) {
eor = current ; break ;
}
2017-09-28 12:40:26 +03:00
[[fallthrough]] ;
2014-05-30 12:53:19 +04:00
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 ;
eAppendItems ( analysis , sor , eor , control , dir ) ;
2016-04-07 01:55:14 +02:00
status . eor = QChar : : DirEN ;
2014-05-30 12:53:19 +04:00
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 ) {
eAppendItems ( analysis , sor , eor , control , dir ) ;
2016-04-07 01:55:14 +02:00
status . eor = QChar : : DirON ;
2014-05-30 12:53:19 +04:00
eor = current - 1 ;
dir = QChar : : DirR ;
eAppendItems ( analysis , sor , eor , control , dir ) ;
2016-04-07 01:55:14 +02:00
status . eor = QChar : : DirON ;
2014-05-30 12:53:19 +04:00
dir = QChar : : DirAN ;
} else {
eor = current ; status . eor = dirCurrent ;
}
}
2017-09-28 12:40:26 +03:00
[[fallthrough]] ;
2014-05-30 12:53:19 +04:00
default :
break ;
}
break ;
}
2017-09-28 12:40:26 +03:00
[[fallthrough]] ;
2014-05-30 12:53:19 +04:00
case QChar : : DirAN :
hasBidi = true ;
dirCurrent = QChar : : DirAN ;
if ( dir = = QChar : : DirON ) dir = QChar : : DirAN ;
switch ( status . last )
{
case QChar : : DirL :
case QChar : : DirAN :
2017-09-28 12:40:26 +03:00
eor = current ; status . eor = QChar : : DirAN ;
break ;
2014-05-30 12:53:19 +04:00
case QChar : : DirR :
case QChar : : DirAL :
case QChar : : DirEN :
if ( eor > = 0 ) {
eAppendItems ( analysis , sor , eor , control , dir ) ;
} else {
eor = current ;
}
dir = QChar : : DirAN ; status . eor = QChar : : DirAN ;
break ;
case QChar : : DirCS :
if ( status . eor = = QChar : : DirAN ) {
eor = current ; break ;
}
2017-09-28 12:40:26 +03:00
[[fallthrough]] ;
2014-05-30 12:53:19 +04:00
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 ;
eAppendItems ( 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 ) {
eAppendItems ( analysis , sor , eor , control , dir ) ;
status . eor = QChar : : DirON ;
eor = current - 1 ;
dir = QChar : : DirR ;
eAppendItems ( analysis , sor , eor , control , dir ) ;
status . eor = QChar : : DirAN ;
dir = QChar : : DirAN ;
} else {
eor = current ; status . eor = dirCurrent ;
}
}
2017-09-28 12:40:26 +03:00
[[fallthrough]] ;
2014-05-30 12:53:19 +04:00
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 ;
}
if ( current > = ( int ) _parLength ) 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 ;
}
2017-09-28 12:40:26 +03:00
[[fallthrough]] ;
2014-05-30 12:53:19 +04:00
default :
status . last = dirCurrent ;
}
+ + current ;
}
eor = current - 1 ; // remove dummy char
if ( sor < = eor )
eAppendItems ( analysis , sor , eor , control , dir ) ;
return hasBidi ;
}
private :
2017-02-09 16:46:14 +03:00
void applyBlockProperties ( ITextBlock * block ) {
eSetFont ( block ) ;
if ( _p ) {
if ( block - > lnkIndex ( ) ) {
2017-02-16 17:07:16 +03:00
_currentPen = & _textPalette - > linkFg - > p ;
_currentPenSelected = & _textPalette - > selectLinkFg - > p ;
2017-02-09 16:46:14 +03:00
} else if ( ( block - > flags ( ) & TextBlockFCode ) | | ( block - > flags ( ) & TextBlockFPre ) ) {
2017-02-16 17:07:16 +03:00
_currentPen = & _textPalette - > monoFg - > p ;
_currentPenSelected = & _textPalette - > selectMonoFg - > p ;
2017-02-09 16:46:14 +03:00
} else {
_currentPen = & _originalPen ;
2017-02-16 17:07:16 +03:00
_currentPenSelected = & _originalPenSelected ;
2017-02-09 16:46:14 +03:00
}
}
}
2014-05-30 12:53:19 +04:00
2017-02-16 17:07:16 +03:00
Painter * _p = nullptr ;
const style : : TextPalette * _textPalette = nullptr ;
const Text * _t = nullptr ;
2016-04-13 21:29:32 +03:00
bool _elideLast = false ;
bool _breakEverywhere = false ;
2017-02-16 17:07:16 +03:00
int _elideRemoveFromEnd = 0 ;
style : : align _align = style : : al_topleft ;
2014-05-30 12:53:19 +04:00
QPen _originalPen ;
2017-02-16 17:07:16 +03:00
QPen _originalPenSelected ;
2017-02-09 16:46:14 +03:00
const QPen * _currentPen = nullptr ;
const QPen * _currentPenSelected = nullptr ;
2017-02-16 17:07:16 +03:00
int _yFrom = 0 ;
int _yTo = 0 ;
int _yToElide = 0 ;
2016-04-13 21:29:32 +03:00
TextSelection _selection = { 0 , 0 } ;
2016-06-09 14:51:24 +03:00
bool _fullWidthSelection = true ;
2016-04-13 21:29:32 +03:00
const QChar * _str = nullptr ;
2014-05-30 12:53:19 +04:00
// current paragraph data
Text : : TextBlocks : : const_iterator _parStartBlock ;
Qt : : LayoutDirection _parDirection ;
2017-02-16 17:07:16 +03:00
int _parStart = 0 ;
int _parLength = 0 ;
bool _parHasBidi = false ;
2014-05-30 12:53:19 +04:00
QVarLengthArray < QScriptAnalysis , 4096 > _parAnalysis ;
// current line data
2017-02-16 17:07:16 +03:00
QTextEngine * _e = nullptr ;
2014-05-30 12:53:19 +04:00
style : : font _f ;
2017-02-26 19:20:17 +03:00
QFixed _x , _w , _wLeft , _last_rPadding ;
2014-05-30 12:53:19 +04:00
int32 _y , _yDelta , _lineHeight , _fontHeight ;
2016-01-07 17:49:54 +08:00
2014-05-30 12:53:19 +04:00
// elided hack support
2017-02-16 17:07:16 +03:00
int _blocksSize = 0 ;
int _elideSavedIndex = 0 ;
2017-07-06 18:58:05 +03:00
std : : unique_ptr < ITextBlock > _elideSavedBlock ;
2014-05-30 12:53:19 +04:00
2017-02-16 17:07:16 +03:00
int _lineStart = 0 ;
int _localFrom = 0 ;
int _lineStartBlock = 0 ;
2014-05-30 12:53:19 +04:00
// link and symbol resolve
2016-04-13 21:29:32 +03:00
QFixed _lookupX = 0 ;
int _lookupY = 0 ;
bool _lookupSymbol = false ;
bool _lookupLink = false ;
Text : : StateRequest _lookupRequest ;
Text : : StateResult _lookupResult ;
2014-05-30 12:53:19 +04:00
} ;
const TextParseOptions _defaultOptions = {
TextParseLinks | TextParseMultiline , // flags
0 , // maxw
0 , // maxh
Qt : : LayoutDirectionAuto , // dir
} ;
const TextParseOptions _textPlainOptions = {
TextParseMultiline , // flags
0 , // maxw
0 , // maxh
Qt : : LayoutDirectionAuto , // dir
} ;
2016-12-23 16:21:01 +03:00
Text : : Text ( int32 minResizeWidth ) : _minResizeWidth ( minResizeWidth ) {
2014-05-30 12:53:19 +04:00
}
2016-12-23 16:21:01 +03:00
Text : : Text ( const style : : TextStyle & st , const QString & text , const TextParseOptions & options , int32 minResizeWidth , bool richText ) : _minResizeWidth ( minResizeWidth ) {
2014-05-30 12:53:19 +04:00
if ( richText ) {
2016-12-23 16:21:01 +03:00
setRichText ( st , text , options ) ;
2014-05-30 12:53:19 +04:00
} else {
2016-12-23 16:21:01 +03:00
setText ( st , text , options ) ;
2014-05-30 12:53:19 +04:00
}
}
2016-03-25 14:29:45 +03:00
Text : : Text ( const Text & other )
: _minResizeWidth ( other . _minResizeWidth )
, _maxWidth ( other . _maxWidth )
, _minHeight ( other . _minHeight )
, _text ( other . _text )
2016-12-23 16:21:01 +03:00
, _st ( other . _st )
2016-03-25 14:29:45 +03:00
, _links ( other . _links )
, _startDir ( other . _startDir ) {
2017-07-06 18:58:05 +03:00
_blocks . reserve ( other . _blocks . size ( ) ) ;
for ( auto & block : other . _blocks ) {
_blocks . push_back ( block - > clone ( ) ) ;
2015-04-04 23:01:34 +03:00
}
}
2016-03-25 14:29:45 +03:00
Text : : Text ( Text & & other )
: _minResizeWidth ( other . _minResizeWidth )
, _maxWidth ( other . _maxWidth )
, _minHeight ( other . _minHeight )
, _text ( other . _text )
2016-12-23 16:21:01 +03:00
, _st ( other . _st )
2017-07-06 18:58:05 +03:00
, _blocks ( std : : move ( other . _blocks ) )
2016-03-25 14:29:45 +03:00
, _links ( other . _links )
, _startDir ( other . _startDir ) {
other . clearFields ( ) ;
}
2015-05-20 22:28:24 +03:00
Text & Text : : operator = ( const Text & other ) {
_minResizeWidth = other . _minResizeWidth ;
_maxWidth = other . _maxWidth ;
_minHeight = other . _minHeight ;
_text = other . _text ;
2016-12-23 16:21:01 +03:00
_st = other . _st ;
2015-05-20 22:28:24 +03:00
_blocks = TextBlocks ( other . _blocks . size ( ) ) ;
_links = other . _links ;
_startDir = other . _startDir ;
for ( int32 i = 0 , l = _blocks . size ( ) ; i < l ; + + i ) {
_blocks [ i ] = other . _blocks . at ( i ) - > clone ( ) ;
}
return * this ;
}
2016-03-25 14:29:45 +03:00
Text & Text : : operator = ( Text & & other ) {
_minResizeWidth = other . _minResizeWidth ;
_maxWidth = other . _maxWidth ;
_minHeight = other . _minHeight ;
_text = other . _text ;
2016-12-23 16:21:01 +03:00
_st = other . _st ;
2017-07-06 18:58:05 +03:00
_blocks = std : : move ( other . _blocks ) ;
2016-03-25 14:29:45 +03:00
_links = other . _links ;
_startDir = other . _startDir ;
other . clearFields ( ) ;
return * this ;
}
2016-12-23 16:21:01 +03:00
void Text : : setText ( const style : : TextStyle & st , const QString & text , const TextParseOptions & options ) {
_st = & st ;
2016-03-25 14:29:45 +03:00
clear ( ) ;
2014-05-30 12:53:19 +04:00
{
TextParser parser ( this , text , options ) ;
}
2015-08-21 14:23:44 +03:00
recountNaturalSize ( true , options . dir ) ;
}
2014-05-30 12:53:19 +04:00
2015-08-21 14:23:44 +03:00
void Text : : recountNaturalSize ( bool initial , Qt : : LayoutDirection optionsDir ) {
2014-05-30 12:53:19 +04:00
NewlineBlock * lastNewline = 0 ;
2015-08-24 13:53:04 +03:00
_maxWidth = _minHeight = 0 ;
2014-05-30 12:53:19 +04:00
int32 lineHeight = 0 ;
int32 result = 0 , lastNewlineStart = 0 ;
QFixed _width = 0 , last_rBearing = 0 , last_rPadding = 0 ;
2017-02-26 19:20:17 +03:00
for ( auto i = _blocks . cbegin ( ) , e = _blocks . cend ( ) ; i ! = e ; + + i ) {
2017-07-06 18:58:05 +03:00
auto b = i - > get ( ) ;
2017-02-26 19:20:17 +03:00
auto _btype = b - > type ( ) ;
auto blockHeight = countBlockHeight ( b , _st ) ;
2015-10-23 18:06:56 +02:00
if ( _btype = = TextBlockTNewline ) {
2014-05-30 12:53:19 +04:00
if ( ! lineHeight ) lineHeight = blockHeight ;
2015-08-21 14:23:44 +03:00
if ( initial ) {
Qt : : LayoutDirection dir = optionsDir ;
if ( dir = = Qt : : LayoutDirectionAuto ) {
dir = TextParser : : stringDirection ( _text , lastNewlineStart , b - > from ( ) ) ;
}
if ( lastNewline ) {
lastNewline - > _nextDir = dir ;
} else {
_startDir = dir ;
}
2014-05-30 12:53:19 +04:00
}
lastNewlineStart = b - > from ( ) ;
lastNewline = static_cast < NewlineBlock * > ( b ) ;
_minHeight + = lineHeight ;
lineHeight = 0 ;
2016-05-02 16:42:09 +03:00
last_rBearing = b - > f_rbearing ( ) ;
2014-05-30 12:53:19 +04:00
last_rPadding = b - > f_rpadding ( ) ;
2017-02-28 19:38:23 +03:00
accumulate_max ( _maxWidth , _width ) ;
2014-05-30 12:53:19 +04:00
_width = ( b - > f_width ( ) - last_rBearing ) ;
continue ;
}
2016-05-02 16:42:09 +03:00
auto b__f_rbearing = b - > f_rbearing ( ) ; // cache
2017-02-28 19:38:23 +03:00
// We need to accumulate max width after each block, because
// some blocks have width less than -1 * previous right bearing.
// In that cases the _width gets _smaller_ after moving to the next block.
//
// But when we layout block and we're sure that _maxWidth is enough
// for all the blocks to fit on their line we check each block, even the
// intermediate one with a large negative right bearing.
accumulate_max ( _maxWidth , _width ) ;
2016-05-02 16:42:09 +03:00
_width + = last_rBearing + ( last_rPadding + b - > f_width ( ) - b__f_rbearing ) ;
2014-05-30 12:53:19 +04:00
lineHeight = qMax ( lineHeight , blockHeight ) ;
2016-05-02 16:42:09 +03:00
last_rBearing = b__f_rbearing ;
2014-05-30 12:53:19 +04:00
last_rPadding = b - > f_rpadding ( ) ;
continue ;
}
2015-08-21 14:23:44 +03:00
if ( initial ) {
Qt : : LayoutDirection dir = optionsDir ;
if ( dir = = Qt : : LayoutDirectionAuto ) {
dir = TextParser : : stringDirection ( _text , lastNewlineStart , _text . size ( ) ) ;
}
if ( lastNewline ) {
lastNewline - > _nextDir = dir ;
} else {
_startDir = dir ;
}
2014-05-30 12:53:19 +04:00
}
if ( _width > 0 ) {
2017-07-06 18:58:05 +03:00
if ( ! lineHeight ) lineHeight = countBlockHeight ( _blocks . back ( ) . get ( ) , _st ) ;
2014-05-30 12:53:19 +04:00
_minHeight + = lineHeight ;
2017-02-28 19:38:23 +03:00
accumulate_max ( _maxWidth , _width ) ;
2014-05-30 12:53:19 +04:00
}
}
2016-12-23 16:21:01 +03:00
void Text : : setMarkedText ( const style : : TextStyle & st , const TextWithEntities & textWithEntities , const TextParseOptions & options ) {
_st = & st ;
2016-03-25 14:29:45 +03:00
clear ( ) ;
2015-08-24 13:53:04 +03:00
{
2017-02-15 11:50:11 +03:00
// utf codes of the text display for emoji extraction
// auto text = textWithEntities.text;
// auto newText = QString();
2015-11-27 20:10:16 +03:00
// newText.reserve(8 * text.size());
2017-02-15 11:50:11 +03:00
// newText.append("\t{ ");
2015-11-27 20:10:16 +03:00
// for (const QChar *ch = text.constData(), *e = ch + text.size(); ch != e; ++ch) {
2017-02-15 11:50:11 +03:00
// if (*ch == TextCommand) {
// break;
// } else if (chIsNewline(*ch)) {
// newText.append("},").append(*ch).append("\t{ ");
2015-11-27 20:10:16 +03:00
// } else {
// if (ch->isHighSurrogate() || ch->isLowSurrogate()) {
// if (ch->isHighSurrogate() && (ch + 1 != e) && ((ch + 1)->isLowSurrogate())) {
2017-02-15 11:50:11 +03:00
// newText.append("0x").append(QString::number((uint32(ch->unicode()) << 16) | uint32((ch + 1)->unicode()), 16).toUpper()).append("U, ");
2015-11-27 20:10:16 +03:00
// ++ch;
// } else {
2017-02-15 11:50:11 +03:00
// newText.append("BADx").append(QString::number(ch->unicode(), 16).toUpper()).append("U, ");
2015-11-27 20:10:16 +03:00
// }
// } else {
2017-02-15 11:50:11 +03:00
// newText.append("0x").append(QString::number(ch->unicode(), 16).toUpper()).append("U, ");
2015-11-27 20:10:16 +03:00
// }
// }
// }
2017-02-15 11:50:11 +03:00
// newText.append("},\n\n").append(text);
// TextParser parser(this, { newText, EntitiesInText() }, options);
2015-11-27 20:10:16 +03:00
2016-05-06 20:33:48 +03:00
TextParser parser ( this , textWithEntities , options ) ;
2015-08-24 13:53:04 +03:00
}
recountNaturalSize ( true , options . dir ) ;
2015-08-21 14:23:44 +03:00
}
2016-12-23 16:21:01 +03:00
void Text : : setRichText ( const style : : TextStyle & st , const QString & text , TextParseOptions options , const TextCustomTagsMap & custom ) {
2014-05-30 12:53:19 +04:00
QString parsed ;
parsed . reserve ( text . size ( ) ) ;
const QChar * s = text . constData ( ) , * ch = s ;
for ( const QChar * b = s , * e = b + text . size ( ) ; ch ! = e ; + + ch ) {
if ( ch - > unicode ( ) = = ' \\ ' ) {
if ( ch > s ) parsed . append ( s , ch - s ) ;
s = ch + 1 ;
if ( s < e ) + + ch ;
continue ;
}
if ( ch - > unicode ( ) = = ' [ ' ) {
if ( ch > s ) parsed . append ( s , ch - s ) ;
s = ch ;
const QChar * tag = ch + 1 ;
if ( tag > = e ) continue ;
bool closing = false , other = false ;
if ( tag - > unicode ( ) = = ' / ' ) {
closing = true ;
if ( + + tag > = e ) continue ;
}
TextCommands cmd ;
switch ( tag - > unicode ( ) ) {
case ' b ' : cmd = closing ? TextCommandNoBold : TextCommandBold ; break ;
case ' i ' : cmd = closing ? TextCommandNoItalic : TextCommandItalic ; break ;
case ' u ' : cmd = closing ? TextCommandNoUnderline : TextCommandUnderline ; break ;
default : other = true ; break ;
}
if ( ! other ) {
if ( + + tag > = e | | tag - > unicode ( ) ! = ' ] ' ) continue ;
parsed . append ( TextCommand ) . append ( QChar ( cmd ) ) . append ( TextCommand ) ;
ch = tag ;
s = ch + 1 ;
continue ;
}
if ( tag - > unicode ( ) ! = ' a ' ) {
TextCustomTagsMap : : const_iterator i = custom . constFind ( * tag ) ;
if ( + + tag > = e | | tag - > unicode ( ) ! = ' ] ' | | i = = custom . cend ( ) ) continue ;
parsed . append ( closing ? i - > second : i - > first ) ;
ch = tag ;
s = ch + 1 ;
continue ;
}
if ( closing ) {
if ( + + tag > = e | | tag - > unicode ( ) ! = ' ] ' ) continue ;
parsed . append ( textcmdStopLink ( ) ) ;
ch = tag ;
s = ch + 1 ;
continue ;
}
if ( + + tag > = e | | tag - > unicode ( ) ! = ' ' ) continue ;
while ( tag < e & & tag - > unicode ( ) = = ' ' ) + + tag ;
if ( tag + 5 < e & & text . midRef ( tag - b , 6 ) = = qsl ( " href= \" " ) ) {
tag + = 6 ;
const QChar * tagend = tag ;
while ( tagend < e & & tagend - > unicode ( ) ! = ' " ' ) + + tagend ;
if ( + + tagend > = e | | tagend - > unicode ( ) ! = ' ] ' ) continue ;
parsed . append ( textcmdStartLink ( QString ( tag , tagend - 1 - tag ) ) ) ;
ch = tagend ;
s = ch + 1 ;
continue ;
}
}
}
if ( ch > s ) parsed . append ( s , ch - s ) ;
s = ch ;
options . flags | = TextParseRichText ;
2016-12-23 16:21:01 +03:00
setText ( st , parsed , options ) ;
2014-05-30 12:53:19 +04:00
}
2016-03-29 20:17:00 +03:00
void Text : : setLink ( uint16 lnkIndex , const ClickHandlerPtr & lnk ) {
2014-05-30 12:53:19 +04:00
if ( ! lnkIndex | | lnkIndex > _links . size ( ) ) return ;
_links [ lnkIndex - 1 ] = lnk ;
}
bool Text : : hasLinks ( ) const {
return ! _links . isEmpty ( ) ;
}
2016-04-14 14:00:23 +03:00
bool Text : : hasSkipBlock ( ) const {
2017-07-06 18:58:05 +03:00
return _blocks . empty ( ) ? false : _blocks . back ( ) - > type ( ) = = TextBlockTSkip ;
2016-04-14 14:00:23 +03:00
}
2015-08-24 13:53:04 +03:00
void Text : : setSkipBlock ( int32 width , int32 height ) {
2017-07-06 18:58:05 +03:00
if ( ! _blocks . empty ( ) & & _blocks . back ( ) - > type ( ) = = TextBlockTSkip ) {
auto block = static_cast < SkipBlock * > ( _blocks . back ( ) . get ( ) ) ;
2015-08-24 13:53:04 +03:00
if ( block - > width ( ) = = width & & block - > height ( ) = = height ) return ;
_text . resize ( block - > from ( ) ) ;
_blocks . pop_back ( ) ;
}
_text . push_back ( ' _ ' ) ;
2017-07-06 18:58:05 +03:00
_blocks . push_back ( std : : make_unique < SkipBlock > ( _st - > font , _text , _text . size ( ) - 1 , width , height , 0 ) ) ;
2015-08-24 13:53:04 +03:00
recountNaturalSize ( false ) ;
2015-08-21 14:23:44 +03:00
}
void Text : : removeSkipBlock ( ) {
2017-07-06 18:58:05 +03:00
if ( ! _blocks . empty ( ) & & _blocks . back ( ) - > type ( ) = = TextBlockTSkip ) {
2015-08-24 13:53:04 +03:00
_text . resize ( _blocks . back ( ) - > from ( ) ) ;
_blocks . pop_back ( ) ;
recountNaturalSize ( false ) ;
}
2015-08-21 14:23:44 +03:00
}
2016-06-09 14:51:24 +03:00
int Text : : countWidth ( int width ) const {
if ( QFixed ( width ) > = _maxWidth ) {
2016-02-07 18:38:49 +03:00
return _maxWidth . ceil ( ) . toInt ( ) ;
}
2016-06-09 14:51:24 +03:00
QFixed maxLineWidth = 0 ;
enumerateLines ( width , [ & maxLineWidth ] ( QFixed lineWidth , int lineHeight ) {
if ( lineWidth > maxLineWidth ) {
maxLineWidth = lineWidth ;
2016-02-07 18:38:49 +03:00
}
2016-06-09 14:51:24 +03:00
} ) ;
return maxLineWidth . ceil ( ) . toInt ( ) ;
}
2016-02-07 18:38:49 +03:00
2016-06-09 14:51:24 +03:00
int Text : : countHeight ( int width ) const {
if ( QFixed ( width ) > = _maxWidth ) {
return _minHeight ;
2016-02-07 18:38:49 +03:00
}
2016-06-09 14:51:24 +03:00
int result = 0 ;
enumerateLines ( width , [ & result ] ( QFixed lineWidth , int lineHeight ) {
result + = lineHeight ;
} ) ;
return result ;
}
2016-02-07 18:38:49 +03:00
2016-06-09 14:51:24 +03:00
void Text : : countLineWidths ( int width , QVector < int > * lineWidths ) const {
enumerateLines ( width , [ lineWidths ] ( QFixed lineWidth , int lineHeight ) {
lineWidths - > push_back ( lineWidth . ceil ( ) . toInt ( ) ) ;
} ) ;
2016-02-07 18:38:49 +03:00
}
2016-06-09 14:51:24 +03:00
template < typename Callback >
void Text : : enumerateLines ( int w , Callback callback ) const {
2014-05-30 12:53:19 +04:00
QFixed width = w ;
if ( width < _minResizeWidth ) width = _minResizeWidth ;
2016-06-09 14:51:24 +03:00
int lineHeight = 0 ;
2014-05-30 12:53:19 +04:00
QFixed widthLeft = width , last_rBearing = 0 , last_rPadding = 0 ;
bool longWordLine = true ;
2017-07-06 18:58:05 +03:00
for ( auto & b : _blocks ) {
2017-02-26 19:20:17 +03:00
auto _btype = b - > type ( ) ;
2017-07-06 18:58:05 +03:00
int blockHeight = countBlockHeight ( b . get ( ) , _st ) ;
2014-05-30 12:53:19 +04:00
2015-10-23 18:06:56 +02:00
if ( _btype = = TextBlockTNewline ) {
2014-05-30 12:53:19 +04:00
if ( ! lineHeight ) lineHeight = blockHeight ;
2016-06-09 14:51:24 +03:00
callback ( width - widthLeft , lineHeight ) ;
2014-05-30 12:53:19 +04:00
lineHeight = 0 ;
2016-05-02 16:42:09 +03:00
last_rBearing = b - > f_rbearing ( ) ;
2014-05-30 12:53:19 +04:00
last_rPadding = b - > f_rpadding ( ) ;
widthLeft = width - ( b - > f_width ( ) - last_rBearing ) ;
longWordLine = true ;
continue ;
}
2016-05-02 16:42:09 +03:00
auto b__f_rbearing = b - > f_rbearing ( ) ;
2017-02-26 19:20:17 +03:00
auto newWidthLeft = widthLeft - last_rBearing - ( last_rPadding + b - > f_width ( ) - b__f_rbearing ) ;
2014-05-30 12:53:19 +04:00
if ( newWidthLeft > = 0 ) {
2016-05-02 16:42:09 +03:00
last_rBearing = b__f_rbearing ;
2014-05-30 12:53:19 +04:00
last_rPadding = b - > f_rpadding ( ) ;
widthLeft = newWidthLeft ;
lineHeight = qMax ( lineHeight , blockHeight ) ;
longWordLine = false ;
continue ;
}
2015-10-23 18:06:56 +02:00
if ( _btype = = TextBlockTText ) {
2017-07-06 18:58:05 +03:00
auto t = static_cast < TextBlock * > ( b . get ( ) ) ;
2016-01-08 11:25:50 +08:00
if ( t - > _words . isEmpty ( ) ) { // no words in this block, spaces only => layout this block in the same line
2017-02-26 19:20:17 +03:00
last_rPadding + = b - > f_rpadding ( ) ;
2016-01-08 11:25:50 +08:00
lineHeight = qMax ( lineHeight , blockHeight ) ;
longWordLine = false ;
continue ;
}
2017-02-26 19:20:17 +03:00
auto f_wLeft = widthLeft ;
2016-06-09 14:51:24 +03:00
int f_lineHeight = lineHeight ;
for ( auto j = t - > _words . cbegin ( ) , e = t - > _words . cend ( ) , f = j ; j ! = e ; + + j ) {
2016-05-02 16:42:09 +03:00
bool wordEndsHere = ( j - > f_width ( ) > = 0 ) ;
2017-02-26 19:20:17 +03:00
auto j_width = wordEndsHere ? j - > f_width ( ) : - j - > f_width ( ) ;
2014-05-30 12:53:19 +04:00
2017-02-26 19:20:17 +03:00
auto newWidthLeft = widthLeft - last_rBearing - ( last_rPadding + j_width - j - > f_rbearing ( ) ) ;
2014-05-30 12:53:19 +04:00
if ( newWidthLeft > = 0 ) {
last_rBearing = j - > f_rbearing ( ) ;
2016-05-02 16:42:09 +03:00
last_rPadding = j - > f_rpadding ( ) ;
2014-05-30 12:53:19 +04:00
widthLeft = newWidthLeft ;
lineHeight = qMax ( lineHeight , blockHeight ) ;
if ( wordEndsHere ) {
longWordLine = false ;
}
if ( wordEndsHere | | longWordLine ) {
f_wLeft = widthLeft ;
f_lineHeight = lineHeight ;
f = j + 1 ;
}
continue ;
}
if ( f ! = j ) {
j = f ;
widthLeft = f_wLeft ;
lineHeight = f_lineHeight ;
2016-05-02 16:42:09 +03:00
j_width = ( j - > f_width ( ) > = 0 ) ? j - > f_width ( ) : - j - > f_width ( ) ;
2014-05-30 12:53:19 +04:00
}
2016-06-09 14:51:24 +03:00
callback ( width - widthLeft , lineHeight ) ;
2014-05-30 12:53:19 +04:00
lineHeight = qMax ( 0 , blockHeight ) ;
last_rBearing = j - > f_rbearing ( ) ;
2016-05-02 16:42:09 +03:00
last_rPadding = j - > f_rpadding ( ) ;
2014-05-30 12:53:19 +04:00
widthLeft = width - ( j_width - last_rBearing ) ;
longWordLine = true ;
f = j + 1 ;
f_wLeft = widthLeft ;
f_lineHeight = lineHeight ;
}
continue ;
}
2016-06-09 14:51:24 +03:00
callback ( width - widthLeft , lineHeight ) ;
2014-05-30 12:53:19 +04:00
lineHeight = qMax ( 0 , blockHeight ) ;
2016-05-02 16:42:09 +03:00
last_rBearing = b__f_rbearing ;
2014-05-30 12:53:19 +04:00
last_rPadding = b - > f_rpadding ( ) ;
widthLeft = width - ( b - > f_width ( ) - last_rBearing ) ;
longWordLine = true ;
continue ;
}
if ( widthLeft < width ) {
2016-06-09 14:51:24 +03:00
callback ( width - widthLeft , lineHeight ) ;
2014-05-30 12:53:19 +04:00
}
}
2016-12-23 16:21:01 +03:00
void Text : : draw ( Painter & painter , int32 left , int32 top , int32 w , style : : align align , int32 yFrom , int32 yTo , TextSelection selection , bool fullWidthSelection ) const {
2014-05-30 12:53:19 +04:00
// painter.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug
TextPainter p ( & painter , this ) ;
2016-06-09 14:51:24 +03:00
p . draw ( left , top , w , align , yFrom , yTo , selection , fullWidthSelection ) ;
2014-05-30 12:53:19 +04:00
}
2016-12-23 16:21:01 +03:00
void Text : : drawElided ( Painter & painter , int32 left , int32 top , int32 w , int32 lines , style : : align align , int32 yFrom , int32 yTo , int32 removeFromEnd , bool breakEverywhere , TextSelection selection ) const {
2014-05-30 12:53:19 +04:00
// painter.fillRect(QRect(left, top, w, countHeight(w)), QColor(0, 0, 0, 32)); // debug
TextPainter p ( & painter , this ) ;
2016-04-13 21:29:32 +03:00
p . drawElided ( left , top , w , align , lines , yFrom , yTo , removeFromEnd , breakEverywhere , selection ) ;
2014-05-30 12:53:19 +04:00
}
2017-06-22 00:38:31 +03:00
Text : : StateResult Text : : getState ( QPoint point , int width , StateRequest request ) const {
2014-05-30 12:53:19 +04:00
TextPainter p ( 0 , this ) ;
2017-06-22 00:38:31 +03:00
return p . getState ( point , width , request ) ;
2014-05-30 12:53:19 +04:00
}
2017-06-22 00:38:31 +03:00
Text : : StateResult Text : : getStateElided ( QPoint point , int width , StateRequestElided request ) const {
2014-05-30 12:53:19 +04:00
TextPainter p ( 0 , this ) ;
2017-06-22 00:38:31 +03:00
return p . getStateElided ( point , width , request ) ;
2014-05-30 12:53:19 +04:00
}
2016-04-13 21:29:32 +03:00
TextSelection Text : : adjustSelection ( TextSelection selection , TextSelectType selectType ) const {
uint16 from = selection . from , to = selection . to ;
2014-05-30 12:53:19 +04:00
if ( from < _text . size ( ) & & from < = to ) {
2016-04-13 21:29:32 +03:00
if ( to > _text . size ( ) ) to = _text . size ( ) ;
2016-05-26 18:31:20 +03:00
if ( selectType = = TextSelectType : : Paragraphs ) {
2014-05-30 12:53:19 +04:00
if ( ! chIsParagraphSeparator ( _text . at ( from ) ) ) {
while ( from > 0 & & ! chIsParagraphSeparator ( _text . at ( from - 1 ) ) ) {
- - from ;
}
}
if ( to < _text . size ( ) ) {
if ( chIsParagraphSeparator ( _text . at ( to ) ) ) {
+ + to ;
} else {
while ( to < _text . size ( ) & & ! chIsParagraphSeparator ( _text . at ( to ) ) ) {
+ + to ;
}
}
}
2016-05-26 18:31:20 +03:00
} else if ( selectType = = TextSelectType : : Words ) {
2014-05-30 12:53:19 +04:00
if ( ! chIsWordSeparator ( _text . at ( from ) ) ) {
while ( from > 0 & & ! chIsWordSeparator ( _text . at ( from - 1 ) ) ) {
- - from ;
}
}
if ( to < _text . size ( ) ) {
if ( chIsWordSeparator ( _text . at ( to ) ) ) {
+ + to ;
} else {
while ( to < _text . size ( ) & & ! chIsWordSeparator ( _text . at ( to ) ) ) {
+ + to ;
}
}
}
}
}
2016-04-13 21:29:32 +03:00
return { from , to } ;
2014-05-30 12:53:19 +04:00
}
2016-09-28 13:15:03 +03:00
bool Text : : isEmpty ( ) const {
return _blocks . empty ( ) | | _blocks [ 0 ] - > type ( ) = = TextBlockTSkip ;
}
2017-02-16 17:07:16 +03:00
uint16 Text : : countBlockEnd ( const TextBlocks : : const_iterator & i , const TextBlocks : : const_iterator & e ) const {
return ( i + 1 = = e ) ? _text . size ( ) : ( * ( i + 1 ) ) - > from ( ) ;
}
uint16 Text : : countBlockLength ( const Text : : TextBlocks : : const_iterator & i , const Text : : TextBlocks : : const_iterator & e ) const {
return countBlockEnd ( i , e ) - ( * i ) - > from ( ) ;
}
2016-05-06 20:33:48 +03:00
template < typename AppendPartCallback , typename ClickHandlerStartCallback , typename ClickHandlerFinishCallback , typename FlagsChangeCallback >
void Text : : enumerateText ( TextSelection selection , AppendPartCallback appendPartCallback , ClickHandlerStartCallback clickHandlerStartCallback , ClickHandlerFinishCallback clickHandlerFinishCallback , FlagsChangeCallback flagsChangeCallback ) const {
2016-04-14 14:00:23 +03:00
if ( isEmpty ( ) | | selection . empty ( ) ) {
2016-05-06 20:33:48 +03:00
return ;
2016-04-14 14:00:23 +03:00
}
2016-05-06 20:33:48 +03:00
int lnkIndex = 0 ;
uint16 lnkFrom = 0 ;
int32 flags = 0 ;
for ( auto i = _blocks . cbegin ( ) , e = _blocks . cend ( ) ; true ; + + i ) {
int blockLnkIndex = ( i = = e ) ? 0 : ( * i ) - > lnkIndex ( ) ;
uint16 blockFrom = ( i = = e ) ? _text . size ( ) : ( * i ) - > from ( ) ;
int32 blockFlags = ( i = = e ) ? 0 : ( * i ) - > flags ( ) ;
2014-05-30 12:53:19 +04:00
2016-05-06 20:33:48 +03:00
bool checkBlockFlags = ( blockFrom > = selection . from ) & & ( blockFrom < = selection . to ) ;
if ( checkBlockFlags & & blockFlags ! = flags ) {
flagsChangeCallback ( flags , blockFlags ) ;
flags = blockFlags ;
}
2016-04-29 15:00:48 +03:00
if ( blockLnkIndex & & ! _links . at ( blockLnkIndex - 1 ) ) { // ignore empty links
blockLnkIndex = 0 ;
}
2014-05-30 12:53:19 +04:00
if ( blockLnkIndex ! = lnkIndex ) {
2016-05-06 20:33:48 +03:00
if ( lnkIndex ) {
auto rangeFrom = qMax ( selection . from , lnkFrom ) ;
auto rangeTo = qMin ( blockFrom , selection . to ) ;
if ( rangeTo > rangeFrom ) { // handle click handler
QStringRef r = _text . midRef ( rangeFrom , rangeTo - rangeFrom ) ;
if ( lnkFrom ! = rangeFrom | | blockFrom ! = rangeTo ) {
appendPartCallback ( r ) ;
2014-05-30 12:53:19 +04:00
} else {
2016-05-06 20:33:48 +03:00
clickHandlerFinishCallback ( r , _links . at ( lnkIndex - 1 ) ) ;
2014-05-30 12:53:19 +04:00
}
}
}
lnkIndex = blockLnkIndex ;
2016-05-06 20:33:48 +03:00
if ( lnkIndex ) {
lnkFrom = blockFrom ;
clickHandlerStartCallback ( ) ;
}
2014-05-30 12:53:19 +04:00
}
2016-05-06 20:33:48 +03:00
if ( i = = e | | blockFrom > = selection . to ) break ;
2014-05-30 12:53:19 +04:00
2016-05-06 20:33:48 +03:00
if ( ( * i ) - > type ( ) = = TextBlockTSkip ) continue ;
2014-05-30 12:53:19 +04:00
if ( ! blockLnkIndex ) {
2016-05-06 20:33:48 +03:00
auto rangeFrom = qMax ( selection . from , blockFrom ) ;
2017-02-16 17:07:16 +03:00
auto rangeTo = qMin ( selection . to , uint16 ( blockFrom + countBlockLength ( i , e ) ) ) ;
2014-05-30 12:53:19 +04:00
if ( rangeTo > rangeFrom ) {
2016-05-06 20:33:48 +03:00
appendPartCallback ( _text . midRef ( rangeFrom , rangeTo - rangeFrom ) ) ;
2014-05-30 12:53:19 +04:00
}
}
}
}
2016-05-06 20:33:48 +03:00
TextWithEntities Text : : originalTextWithEntities ( TextSelection selection , ExpandLinksMode mode ) const {
TextWithEntities result ;
result . text . reserve ( _text . size ( ) ) ;
int lnkStart = 0 , italicStart = 0 , boldStart = 0 , codeStart = 0 , preStart = 0 ;
auto flagsChangeCallback = [ & result , & italicStart , & boldStart , & codeStart , & preStart ] ( int32 oldFlags , int32 newFlags ) {
if ( ( oldFlags & TextBlockFItalic ) & & ! ( newFlags & TextBlockFItalic ) ) { // write italic
result . entities . push_back ( EntityInText ( EntityInTextItalic , italicStart , result . text . size ( ) - italicStart ) ) ;
} else if ( ( newFlags & TextBlockFItalic ) & & ! ( oldFlags & TextBlockFItalic ) ) {
italicStart = result . text . size ( ) ;
2015-10-25 18:08:45 +01:00
}
2016-05-06 20:33:48 +03:00
if ( ( oldFlags & TextBlockFSemibold ) & & ! ( newFlags & TextBlockFSemibold ) ) {
result . entities . push_back ( EntityInText ( EntityInTextBold , boldStart , result . text . size ( ) - boldStart ) ) ;
} else if ( ( newFlags & TextBlockFSemibold ) & & ! ( oldFlags & TextBlockFSemibold ) ) {
boldStart = result . text . size ( ) ;
2016-04-29 15:00:48 +03:00
}
2016-05-06 20:33:48 +03:00
if ( ( oldFlags & TextBlockFCode ) & & ! ( newFlags & TextBlockFCode ) ) {
result . entities . push_back ( EntityInText ( EntityInTextCode , codeStart , result . text . size ( ) - codeStart ) ) ;
} else if ( ( newFlags & TextBlockFCode ) & & ! ( oldFlags & TextBlockFCode ) ) {
codeStart = result . text . size ( ) ;
2015-10-25 18:08:45 +01:00
}
2016-05-06 20:33:48 +03:00
if ( ( oldFlags & TextBlockFPre ) & & ! ( newFlags & TextBlockFPre ) ) {
result . entities . push_back ( EntityInText ( EntityInTextPre , preStart , result . text . size ( ) - preStart ) ) ;
} else if ( ( newFlags & TextBlockFPre ) & & ! ( oldFlags & TextBlockFPre ) ) {
preStart = result . text . size ( ) ;
}
} ;
auto clickHandlerStartCallback = [ & result , & lnkStart ] ( ) {
lnkStart = result . text . size ( ) ;
} ;
auto clickHandlerFinishCallback = [ mode , & result , & lnkStart ] ( const QStringRef & r , const ClickHandlerPtr & handler ) {
auto expanded = handler - > getExpandedLinkTextWithEntities ( mode , lnkStart , r ) ;
if ( expanded . text . isEmpty ( ) ) {
result . text + = r ;
} else {
result . text + = expanded . text ;
}
if ( ! expanded . entities . isEmpty ( ) ) {
result . entities . append ( expanded . entities ) ;
}
} ;
auto appendPartCallback = [ & result ] ( const QStringRef & r ) {
result . text + = r ;
} ;
2015-10-25 18:08:45 +01:00
2016-05-06 20:33:48 +03:00
enumerateText ( selection , appendPartCallback , clickHandlerStartCallback , clickHandlerFinishCallback , flagsChangeCallback ) ;
2015-10-25 18:08:45 +01:00
2016-05-06 20:33:48 +03:00
return result ;
}
QString Text : : originalText ( TextSelection selection , ExpandLinksMode mode ) const {
QString result ;
result . reserve ( _text . size ( ) ) ;
auto appendPartCallback = [ & result ] ( const QStringRef & r ) {
result + = r ;
} ;
auto clickHandlerStartCallback = [ ] ( ) {
} ;
auto clickHandlerFinishCallback = [ mode , & result ] ( const QStringRef & r , const ClickHandlerPtr & handler ) {
auto expanded = handler - > getExpandedLinkText ( mode , r ) ;
if ( expanded . isEmpty ( ) ) {
result + = r ;
} else {
result + = expanded ;
2015-10-25 18:08:45 +01:00
}
2016-05-06 20:33:48 +03:00
} ;
auto flagsChangeCallback = [ ] ( int32 oldFlags , int32 newFlags ) {
} ;
enumerateText ( selection , appendPartCallback , clickHandlerStartCallback , clickHandlerFinishCallback , flagsChangeCallback ) ;
2015-10-25 18:08:45 +01:00
return result ;
}
2016-03-25 14:29:45 +03:00
void Text : : clear ( ) {
clearFields ( ) ;
2016-06-03 21:24:27 +03:00
_text . clear ( ) ;
2016-03-25 14:29:45 +03:00
}
void Text : : clearFields ( ) {
2014-05-30 12:53:19 +04:00
_blocks . clear ( ) ;
_links . clear ( ) ;
_maxWidth = _minHeight = 0 ;
_startDir = Qt : : LayoutDirectionAuto ;
}
2017-07-06 18:58:05 +03:00
Text : : ~ Text ( ) = default ;
2015-05-08 15:45:14 +03:00
void emojiDraw ( QPainter & p , EmojiPtr e , int x , int y ) {
2017-02-15 11:50:11 +03:00
auto size = Ui : : Emoji : : Size ( ) ;
p . drawPixmap ( QPoint ( x , y ) , App : : emoji ( ) , QRect ( e - > x ( ) * size , e - > y ( ) * size , size , size ) ) ;
2015-05-08 15:45:14 +03:00
}