2014-05-30 08:53:19 +00:00
/*
This file is part of Telegram Desktop ,
2014-12-01 10:47:38 +00:00
the official desktop version of Telegram messaging app , see https : //telegram.org
2014-05-30 08:53:19 +00: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 13:16:42 +00: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 08:53:19 +00:00
Full license : https : //github.com/telegramdesktop/tdesktop/blob/master/LICENSE
2016-02-08 10:56:18 +00:00
Copyright ( c ) 2014 - 2016 John Preston , https : //desktop.telegram.org
2014-05-30 08:53:19 +00:00
*/
# pragma once
2016-04-23 11:40:42 +00:00
# include "private/qfontengine_p.h"
2014-05-30 08:53:19 +00:00
2016-04-14 11:00:23 +00:00
# include "core/click_handler.h"
# include "ui/text/text_entity.h"
# include "ui/emoji_config.h"
2014-05-30 08:53:19 +00:00
static const QChar TextCommand ( 0x0010 ) ;
enum TextCommands {
TextCommandBold = 0x01 ,
TextCommandNoBold = 0x02 ,
TextCommandItalic = 0x03 ,
TextCommandNoItalic = 0x04 ,
TextCommandUnderline = 0x05 ,
TextCommandNoUnderline = 0x06 ,
2016-03-04 22:04:15 +00:00
TextCommandSemibold = 0x07 ,
TextCommandNoSemibold = 0x08 ,
TextCommandLinkIndex = 0x09 , // 0 - NoLink
TextCommandLinkText = 0x0A ,
TextCommandColor = 0x0B ,
TextCommandNoColor = 0x0C ,
TextCommandSkipBlock = 0x0D ,
2014-12-18 18:40:49 +00:00
TextCommandLangTag = 0x20 ,
2014-05-30 08:53:19 +00:00
} ;
struct TextParseOptions {
int32 flags ;
int32 maxw ;
int32 maxh ;
Qt : : LayoutDirection dir ;
} ;
2015-06-15 17:19:24 +00:00
extern const TextParseOptions _defaultOptions , _textPlainOptions ;
2014-05-30 08:53:19 +00:00
2016-05-26 15:31:20 +00:00
enum class TextSelectType {
Letters = 0x01 ,
Words = 0x02 ,
Paragraphs = 0x03 ,
2014-05-30 08:53:19 +00:00
} ;
2016-04-13 18:29:32 +00:00
struct TextSelection {
constexpr TextSelection ( ) : from ( 0 ) , to ( 0 ) {
}
constexpr TextSelection ( uint16 from , uint16 to ) : from ( from ) , to ( to ) {
}
2016-04-14 11:00:23 +00:00
constexpr bool empty ( ) const {
return from = = to ;
}
2016-04-13 18:29:32 +00:00
uint16 from : 16 ;
uint16 to : 16 ;
} ;
inline bool operator = = ( TextSelection a , TextSelection b ) {
return a . from = = b . from & & a . to = = b . to ;
}
inline bool operator ! = ( TextSelection a , TextSelection b ) {
return ! ( a = = b ) ;
}
static constexpr TextSelection AllTextSelection = { 0 , 0xFFFF } ;
2014-05-30 08:53:19 +00:00
typedef QPair < QString , QString > TextCustomTag ; // open str and close str
typedef QMap < QChar , TextCustomTag > TextCustomTagsMap ;
2016-04-14 11:00:23 +00:00
class ITextBlock ;
2014-05-30 08:53:19 +00:00
class Text {
public :
Text ( int32 minResizeWidth = QFIXED_MAX ) ;
Text ( style : : font font , const QString & text , const TextParseOptions & options = _defaultOptions , int32 minResizeWidth = QFIXED_MAX , bool richText = false ) ;
2015-04-04 20:01:34 +00:00
Text ( const Text & other ) ;
2016-03-25 11:29:45 +00:00
Text ( Text & & other ) ;
2015-05-20 19:28:24 +00:00
Text & operator = ( const Text & other ) ;
2016-03-25 11:29:45 +00:00
Text & operator = ( Text & & other ) ;
2014-05-30 08:53:19 +00:00
2016-06-09 11:51:24 +00:00
int countWidth ( int width ) const ;
int countHeight ( int width ) const ;
void countLineWidths ( int width , QVector < int > * lineWidths ) const ;
2014-05-30 08:53:19 +00:00
void setText ( style : : font font , const QString & text , const TextParseOptions & options = _defaultOptions ) ;
void setRichText ( style : : font font , const QString & text , TextParseOptions options = _defaultOptions , const TextCustomTagsMap & custom = TextCustomTagsMap ( ) ) ;
2016-05-06 17:33:48 +00:00
void setMarkedText ( style : : font font , const TextWithEntities & textWithEntities , const TextParseOptions & options = _defaultOptions ) ;
2014-05-30 08:53:19 +00:00
2016-03-29 17:17:00 +00:00
void setLink ( uint16 lnkIndex , const ClickHandlerPtr & lnk ) ;
2014-05-30 08:53:19 +00:00
bool hasLinks ( ) const ;
2016-04-14 11:00:23 +00:00
bool hasSkipBlock ( ) const ;
2015-08-24 10:53:04 +00:00
void setSkipBlock ( int32 width , int32 height ) ;
2015-08-21 11:23:44 +00:00
void removeSkipBlock ( ) ;
2015-05-20 19:28:24 +00:00
2014-05-30 08:53:19 +00:00
int32 maxWidth ( ) const {
2014-06-14 19:32:11 +00:00
return _maxWidth . ceil ( ) . toInt ( ) ;
2014-05-30 08:53:19 +00:00
}
int32 minHeight ( ) const {
return _minHeight ;
}
2014-08-11 09:03:45 +00:00
void replaceFont ( style : : font f ) ; // does not recount anything, use at your own risk!
2016-06-09 11:51:24 +00:00
void draw ( QPainter & p , int32 left , int32 top , int32 width , style : : align align = style : : al_left , int32 yFrom = 0 , int32 yTo = - 1 , TextSelection selection = { 0 , 0 } , bool fullWidthSelection = true ) const ;
2016-04-13 18:29:32 +00:00
void drawElided ( QPainter & p , int32 left , int32 top , int32 width , int32 lines = 1 , style : : align align = style : : al_left , int32 yFrom = 0 , int32 yTo = - 1 , int32 removeFromEnd = 0 , bool breakEverywhere = false , TextSelection selection = { 0 , 0 } ) const ;
void drawLeft ( QPainter & p , int32 left , int32 top , int32 width , int32 outerw , style : : align align = style : : al_left , int32 yFrom = 0 , int32 yTo = - 1 , TextSelection selection = { 0 , 0 } ) const {
draw ( p , rtl ( ) ? ( outerw - left - width ) : left , top , width , align , yFrom , yTo , selection ) ;
2015-10-14 11:51:37 +00:00
}
2016-04-13 18:29:32 +00:00
void drawLeftElided ( QPainter & p , int32 left , int32 top , int32 width , int32 outerw , int32 lines = 1 , style : : align align = style : : al_left , int32 yFrom = 0 , int32 yTo = - 1 , int32 removeFromEnd = 0 , bool breakEverywhere = false , TextSelection selection = { 0 , 0 } ) const {
drawElided ( p , rtl ( ) ? ( outerw - left - width ) : left , top , width , lines , align , yFrom , yTo , removeFromEnd , breakEverywhere , selection ) ;
2015-10-14 11:51:37 +00:00
}
2016-04-13 18:29:32 +00:00
void drawRight ( QPainter & p , int32 right , int32 top , int32 width , int32 outerw , style : : align align = style : : al_left , int32 yFrom = 0 , int32 yTo = - 1 , TextSelection selection = { 0 , 0 } ) const {
draw ( p , rtl ( ) ? right : ( outerw - right - width ) , top , width , align , yFrom , yTo , selection ) ;
2015-10-14 11:51:37 +00:00
}
2016-04-13 18:29:32 +00:00
void drawRightElided ( QPainter & p , int32 right , int32 top , int32 width , int32 outerw , int32 lines = 1 , style : : align align = style : : al_left , int32 yFrom = 0 , int32 yTo = - 1 , int32 removeFromEnd = 0 , bool breakEverywhere = false , TextSelection selection = { 0 , 0 } ) const {
drawElided ( p , rtl ( ) ? right : ( outerw - right - width ) , top , width , lines , align , yFrom , yTo , removeFromEnd , breakEverywhere , selection ) ;
2015-10-14 11:51:37 +00:00
}
2014-05-30 08:53:19 +00:00
2016-04-13 18:29:32 +00:00
struct StateRequest {
enum class Flag {
BreakEverywhere = 0x01 ,
LookupSymbol = 0x02 ,
LookupLink = 0x04 ,
} ;
Q_DECLARE_FLAGS ( Flags , Flag ) ;
2016-04-14 13:03:03 +00:00
StateRequest ( ) {
}
2016-04-13 18:29:32 +00:00
style : : align align = style : : al_left ;
Flags flags = Flag : : LookupLink ;
} ;
struct StateResult {
ClickHandlerPtr link ;
bool uponSymbol = false ;
bool afterSymbol = false ;
uint16 symbol = 0 ;
} ;
StateResult getState ( int x , int y , int width , StateRequest request = StateRequest ( ) ) const ;
StateResult getStateLeft ( int x , int y , int width , int outerw , StateRequest request = StateRequest ( ) ) const {
return getState ( rtl ( ) ? ( outerw - x - width ) : x , y , width , request ) ;
2015-10-14 11:51:37 +00:00
}
2016-04-13 18:29:32 +00:00
struct StateRequestElided : public StateRequest {
2016-04-14 19:24:42 +00:00
StateRequestElided ( ) {
}
StateRequestElided ( const StateRequest & other ) : StateRequest ( other ) {
2016-04-13 18:29:32 +00:00
}
int lines = 1 ;
int removeFromEnd = 0 ;
2016-04-14 14:30:47 +00:00
} ;
2016-04-13 18:29:32 +00:00
StateResult getStateElided ( int x , int y , int width , StateRequestElided request = StateRequestElided ( ) ) const ;
StateResult getStateElidedLeft ( int x , int y , int width , int outerw , StateRequestElided request = StateRequestElided ( ) ) const {
return getStateElided ( rtl ( ) ? ( outerw - x - width ) : x , y , width , request ) ;
2015-10-14 11:51:37 +00:00
}
2016-04-13 18:29:32 +00:00
TextSelection adjustSelection ( TextSelection selection , TextSelectType selectType ) const ;
2016-05-26 15:31:20 +00:00
bool isFullSelection ( TextSelection selection ) const {
return ( selection . from = = 0 ) & & ( selection . to > = _text . size ( ) ) ;
}
2014-05-30 08:53:19 +00:00
2015-04-04 20:01:34 +00:00
bool isEmpty ( ) const {
return _text . isEmpty ( ) ;
}
2015-06-25 10:12:38 +00:00
bool isNull ( ) const {
return ! _font ;
}
2016-04-13 18:29:32 +00:00
int length ( ) const {
return _text . size ( ) ;
}
2016-05-06 17:33:48 +00:00
TextWithEntities originalTextWithEntities ( TextSelection selection = AllTextSelection , ExpandLinksMode mode = ExpandLinksShortened ) const ;
QString originalText ( TextSelection selection = AllTextSelection , ExpandLinksMode mode = ExpandLinksShortened ) const ;
2014-05-30 08:53:19 +00:00
2014-06-16 09:31:10 +00:00
bool lastDots ( int32 dots , int32 maxdots = 3 ) { // hack for typing animation
2014-05-30 08:53:19 +00:00
if ( _text . size ( ) < maxdots ) return false ;
int32 nowDots = 0 , from = _text . size ( ) - maxdots , to = _text . size ( ) ;
for ( int32 i = from ; i < to ; + + i ) {
if ( _text . at ( i ) = = QChar ( ' . ' ) ) {
+ + nowDots ;
}
}
if ( nowDots = = dots ) return false ;
for ( int32 j = from ; j < from + dots ; + + j ) {
_text [ j ] = QChar ( ' . ' ) ;
}
for ( int32 j = from + dots ; j < to ; + + j ) {
_text [ j ] = QChar ( ' ' ) ;
}
return true ;
}
2016-03-25 11:29:45 +00:00
void clear ( ) ;
2014-05-30 08:53:19 +00:00
~ Text ( ) {
2016-03-25 11:29:45 +00:00
clear ( ) ;
2014-05-30 08:53:19 +00:00
}
private :
2016-05-06 17:33:48 +00:00
// Template method for originalText(), originalTextWithEntities().
template < typename AppendPartCallback , typename ClickHandlerStartCallback , typename ClickHandlerFinishCallback , typename FlagsChangeCallback >
void enumerateText ( TextSelection selection , AppendPartCallback appendPartCallback , ClickHandlerStartCallback clickHandlerStartCallback , ClickHandlerFinishCallback clickHandlerFinishCallback , FlagsChangeCallback flagsChangeCallback ) const ;
2016-06-09 11:51:24 +00:00
// Template method for countWidth(), countHeight(), countLineWidths().
// callback(lineWidth, lineHeight) will be called for all lines with:
// QFixed lineWidth, int lineHeight
template < typename Callback >
void enumerateLines ( int w , Callback callback ) const ;
2015-08-21 11:23:44 +00:00
void recountNaturalSize ( bool initial , Qt : : LayoutDirection optionsDir = Qt : : LayoutDirectionAuto ) ;
2016-03-25 11:29:45 +00:00
// clear() deletes all blocks and calls this method
// it is also called from move constructor / assignment operator
void clearFields ( ) ;
2014-05-30 08:53:19 +00:00
QFixed _minResizeWidth , _maxWidth ;
int32 _minHeight ;
QString _text ;
style : : font _font ;
typedef QVector < ITextBlock * > TextBlocks ;
TextBlocks _blocks ;
2016-03-29 17:17:00 +00:00
typedef QVector < ClickHandlerPtr > TextLinks ;
2014-05-30 08:53:19 +00:00
TextLinks _links ;
Qt : : LayoutDirection _startDir ;
friend class TextParser ;
friend class TextPainter ;
} ;
2016-04-13 18:29:32 +00:00
inline TextSelection snapSelection ( int from , int to ) {
return { static_cast < uint16 > ( snap ( from , 0 , 0xFFFF ) ) , static_cast < uint16 > ( snap ( to , 0 , 0xFFFF ) ) } ;
}
inline TextSelection shiftSelection ( TextSelection selection , const Text & byText ) {
int len = byText . length ( ) ;
return snapSelection ( int ( selection . from ) + len , int ( selection . to ) + len ) ;
}
inline TextSelection unshiftSelection ( TextSelection selection , const Text & byText ) {
int len = byText . length ( ) ;
return snapSelection ( int ( selection . from ) - len , int ( selection . to ) - len ) ;
}
2014-05-30 08:53:19 +00:00
2015-04-06 22:15:29 +00:00
void initLinkSets ( ) ;
const QSet < int32 > & validProtocols ( ) ;
const QSet < int32 > & validTopDomains ( ) ;
const QRegularExpression & reDomain ( ) ;
const QRegularExpression & reMailName ( ) ;
2015-09-10 11:20:28 +00:00
const QRegularExpression & reMailStart ( ) ;
2015-03-24 10:00:27 +00:00
const QRegularExpression & reHashtag ( ) ;
2015-06-10 15:54:24 +00:00
const QRegularExpression & reBotCommand ( ) ;
2015-03-24 10:00:27 +00:00
2014-05-30 08:53:19 +00:00
// text style
const style : : textStyle * textstyleCurrent ( ) ;
void textstyleSet ( const style : : textStyle * style ) ;
inline void textstyleRestore ( ) {
2016-04-14 11:00:23 +00:00
textstyleSet ( nullptr ) ;
2014-05-30 08:53:19 +00:00
}
// textcmd
QString textcmdSkipBlock ( ushort w , ushort h ) ;
QString textcmdStartLink ( ushort lnkIndex ) ;
QString textcmdStartLink ( const QString & url ) ;
QString textcmdStopLink ( ) ;
QString textcmdLink ( ushort lnkIndex , const QString & text ) ;
QString textcmdLink ( const QString & url , const QString & text ) ;
QString textcmdStartColor ( const style : : color & color ) ;
QString textcmdStopColor ( ) ;
2016-03-04 22:04:15 +00:00
QString textcmdStartSemibold ( ) ;
QString textcmdStopSemibold ( ) ;
2014-12-18 18:40:49 +00:00
const QChar * textSkipCommand ( const QChar * from , const QChar * end , bool canLink = true ) ;
2015-01-05 20:17:33 +00:00
2015-04-06 22:15:29 +00:00
inline bool chIsSpace ( QChar ch , bool rich = false ) {
2016-04-21 17:57:29 +00:00
return ch . isSpace ( ) | | ( ch < 32 & & ! ( rich & & ch = = TextCommand ) ) | | ( ch = = QChar : : ParagraphSeparator ) | | ( ch = = QChar : : LineSeparator ) | | ( ch = = QChar : : ObjectReplacementCharacter ) | | ( ch = = QChar : : CarriageReturn ) | | ( ch = = QChar : : Tabulation ) ;
2015-04-06 22:15:29 +00:00
}
2015-10-23 20:02:29 +00:00
inline bool chIsDiac ( QChar ch ) { // diac and variation selectors
2016-06-27 16:25:21 +00:00
return ( ch . category ( ) = = QChar : : Mark_NonSpacing ) | | ( ch = = 1652 ) | | ( ch > = 64606 & & ch < = 64611 ) ;
2015-10-23 20:02:29 +00:00
}
2015-04-06 22:15:29 +00:00
inline bool chIsBad ( QChar ch ) {
2015-12-03 18:16:34 +00:00
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
2015-04-06 22:15:29 +00:00
}
inline bool chIsTrimmed ( QChar ch , bool rich = false ) {
return ( ! rich | | ch ! = TextCommand ) & & ( chIsSpace ( ch ) | | chIsBad ( ch ) ) ;
}
2015-10-23 16:06:56 +00:00
inline bool chReplacedBySpace ( QChar ch ) {
// \xe2\x80[\xa8 - \xac\xad] // 8232 - 8237
// QString from1 = QString::fromUtf8("\xe2\x80\xa8"), to1 = QString::fromUtf8("\xe2\x80\xad");
// \xcc[\xb3\xbf\x8a] // 819, 831, 778
// QString bad1 = QString::fromUtf8("\xcc\xb3"), bad2 = QString::fromUtf8("\xcc\xbf"), bad3 = QString::fromUtf8("\xcc\x8a");
// [\x00\x01\x02\x07\x08\x0b-\x1f] // '\t' = 0x09
return ( /*code >= 0x00 && */ ch < = 0x02 ) | | ( ch > = 0x07 & & ch < = 0x09 ) | | ( ch > = 0x0b & & ch < = 0x1f ) | |
( ch = = 819 ) | | ( ch = = 831 ) | | ( ch = = 778 ) | | ( ch > = 8232 & & ch < = 8237 ) ;
}
2015-04-06 22:15:29 +00:00
inline int32 chMaxDiacAfterSymbol ( ) {
2015-05-11 10:18:57 +00:00
return 2 ;
2015-04-06 22:15:29 +00:00
}
inline bool chIsNewline ( QChar ch ) {
return ( ch = = QChar : : LineFeed | | ch = = 156 ) ;
}
inline bool chIsLinkEnd ( QChar ch ) {
return ch = = TextCommand | | chIsBad ( ch ) | | chIsSpace ( ch ) | | chIsNewline ( ch ) | | ch . isLowSurrogate ( ) | | ch . isHighSurrogate ( ) ;
}
inline bool chIsAlmostLinkEnd ( QChar ch ) {
switch ( ch . unicode ( ) ) {
case ' ? ' :
case ' , ' :
case ' . ' :
case ' " ' :
case ' : ' :
case ' ! ' :
case ' \' ' :
return true ;
default :
break ;
}
return false ;
}
inline bool chIsWordSeparator ( QChar ch ) {
switch ( ch . unicode ( ) ) {
case QChar : : Space :
case QChar : : LineFeed :
case ' . ' :
case ' , ' :
case ' ? ' :
case ' ! ' :
case ' @ ' :
case ' # ' :
case ' $ ' :
case ' : ' :
case ' ; ' :
case ' - ' :
case ' < ' :
case ' > ' :
case ' [ ' :
case ' ] ' :
case ' ( ' :
case ' ) ' :
case ' { ' :
case ' } ' :
case ' = ' :
case ' / ' :
case ' + ' :
case ' % ' :
case ' & ' :
case ' ^ ' :
case ' * ' :
case ' \' ' :
case ' " ' :
case ' ` ' :
case ' ~ ' :
case ' | ' :
return true ;
default :
break ;
}
return false ;
}
inline bool chIsSentenceEnd ( QChar ch ) {
switch ( ch . unicode ( ) ) {
case ' . ' :
case ' ? ' :
case ' ! ' :
return true ;
default :
break ;
}
return false ;
}
inline bool chIsSentencePartEnd ( QChar ch ) {
switch ( ch . unicode ( ) ) {
case ' , ' :
case ' : ' :
case ' ; ' :
return true ;
default :
break ;
}
return false ;
}
inline bool chIsParagraphSeparator ( QChar ch ) {
switch ( ch . unicode ( ) ) {
case QChar : : LineFeed :
return true ;
default :
break ;
}
return false ;
}
2015-10-18 12:49:34 +00:00
2016-04-14 11:00:23 +00:00
void emojiDraw ( QPainter & p , EmojiPtr e , int x , int y ) ;