1 /***************************************************************************
2   qgstextdocument.cpp
3   -----------------
4    begin                : May 2020
5    copyright            : (C) Nyall Dawson
6    email                : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgstextdocument.h"
17 #include "qgis.h"
18 #include "qgsstringutils.h"
19 #include "qgstextblock.h"
20 #include "qgstextfragment.h"
21 #include <QTextDocument>
22 #include <QTextBlock>
23 
24 QgsTextDocument::~QgsTextDocument() = default;
25 
26 QgsTextDocument::QgsTextDocument() = default;
27 
QgsTextDocument(const QgsTextBlock & block)28 QgsTextDocument::QgsTextDocument( const QgsTextBlock &block )
29 {
30   mBlocks.append( block );
31 }
32 
QgsTextDocument(const QgsTextFragment & fragment)33 QgsTextDocument::QgsTextDocument( const QgsTextFragment &fragment )
34 {
35   mBlocks.append( QgsTextBlock( fragment ) );
36 }
37 
fromPlainText(const QStringList & lines)38 QgsTextDocument QgsTextDocument::fromPlainText( const QStringList &lines )
39 {
40   QgsTextDocument document;
41   document.reserve( lines.size() );
42   for ( const QString &line : lines )
43     document.append( QgsTextBlock( QgsTextFragment( line ) ) );
44   return document;
45 }
46 
fromHtml(const QStringList & lines)47 QgsTextDocument QgsTextDocument::fromHtml( const QStringList &lines )
48 {
49   QgsTextDocument document;
50 
51   document.reserve( lines.size() );
52   for ( const QString &line : lines )
53   {
54     // QTextDocument is a very heavy way of parsing HTML + css (it's heavily geared toward an editable text document,
55     // and includes a LOT of calculations we don't need, when all we're after is a HTML + CSS style parser).
56     // TODO - try to find an alternative library we can use here
57     QTextDocument sourceDoc;
58     sourceDoc.setHtml( line );
59 
60     QTextBlock sourceBlock = sourceDoc.firstBlock();
61     while ( true )
62     {
63       auto it = sourceBlock.begin();
64       QgsTextBlock block;
65       while ( !it.atEnd() )
66       {
67         const QTextFragment fragment = it.fragment();
68         if ( fragment.isValid() )
69         {
70           block.append( QgsTextFragment( fragment ) );
71         }
72         it++;
73       }
74       if ( !block.empty() )
75         document.append( block );
76 
77       sourceBlock = sourceBlock.next();
78       if ( !sourceBlock.isValid() )
79         break;
80     }
81   }
82   return document;
83 }
84 
append(const QgsTextBlock & block)85 void QgsTextDocument::append( const QgsTextBlock &block )
86 {
87   mBlocks.append( block );
88 }
89 
append(QgsTextBlock && block)90 void QgsTextDocument::append( QgsTextBlock &&block )
91 {
92   mBlocks.push_back( block );
93 }
94 
reserve(int count)95 void QgsTextDocument::reserve( int count )
96 {
97   mBlocks.reserve( count );
98 }
99 
at(int i) const100 const QgsTextBlock &QgsTextDocument::at( int i ) const
101 {
102   return mBlocks.at( i );
103 }
104 
operator [](int i)105 QgsTextBlock &QgsTextDocument::operator[]( int i )
106 {
107   return mBlocks[i];
108 }
109 
size() const110 int QgsTextDocument::size() const
111 {
112   return mBlocks.size();
113 }
114 
toPlainText() const115 QStringList QgsTextDocument::toPlainText() const
116 {
117   QStringList textLines;
118   textLines.reserve( mBlocks.size() );
119   for ( const QgsTextBlock &block : mBlocks )
120   {
121     QString line;
122     for ( const QgsTextFragment &fragment : block )
123     {
124       line.append( fragment.text() );
125     }
126     textLines << line;
127   }
128   return textLines;
129 }
130 
splitLines(const QString & wrapCharacter,int autoWrapLength,bool useMaxLineLengthWhenAutoWrapping)131 void QgsTextDocument::splitLines( const QString &wrapCharacter, int autoWrapLength, bool useMaxLineLengthWhenAutoWrapping )
132 {
133   const QVector< QgsTextBlock > prevBlocks = mBlocks;
134   mBlocks.clear();
135   mBlocks.reserve( prevBlocks.size() );
136   for ( const QgsTextBlock &block : prevBlocks )
137   {
138     QgsTextBlock destinationBlock;
139     for ( const QgsTextFragment &fragment : block )
140     {
141       QStringList thisParts;
142       if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) )
143       {
144         //wrap on both the wrapchr and new line characters
145         const QStringList lines = fragment.text().split( wrapCharacter );
146         for ( const QString &line : lines )
147         {
148           thisParts.append( line.split( '\n' ) );
149         }
150       }
151       else
152       {
153         thisParts = fragment.text().split( '\n' );
154       }
155 
156       // apply auto wrapping to each manually created line
157       if ( autoWrapLength != 0 )
158       {
159         QStringList autoWrappedLines;
160         autoWrappedLines.reserve( thisParts.count() );
161         for ( const QString &line : qgis::as_const( thisParts ) )
162         {
163           autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) );
164         }
165         thisParts = autoWrappedLines;
166       }
167 
168       if ( thisParts.empty() )
169         continue;
170       else if ( thisParts.size() == 1 )
171         destinationBlock.append( fragment );
172       else
173       {
174         if ( !thisParts.at( 0 ).isEmpty() )
175           destinationBlock.append( QgsTextFragment( thisParts.at( 0 ), fragment.characterFormat() ) );
176 
177         append( destinationBlock );
178         destinationBlock.clear();
179         for ( int i = 1 ; i < thisParts.size() - 1; ++i )
180         {
181           append( QgsTextBlock( QgsTextFragment( thisParts.at( i ), fragment.characterFormat() ) ) );
182         }
183         destinationBlock.append( QgsTextFragment( thisParts.at( thisParts.size() - 1 ), fragment.characterFormat() ) );
184       }
185     }
186     append( destinationBlock );
187   }
188 }
189 
applyCapitalization(QgsStringUtils::Capitalization capitalization)190 void QgsTextDocument::applyCapitalization( QgsStringUtils::Capitalization capitalization )
191 {
192   for ( QgsTextBlock &block : mBlocks )
193   {
194     block.applyCapitalization( capitalization );
195   }
196 }
197 
198 ///@cond PRIVATE
begin() const199 QVector< QgsTextBlock >::const_iterator QgsTextDocument::begin() const
200 {
201   return mBlocks.begin();
202 }
203 
end() const204 QVector< QgsTextBlock >::const_iterator QgsTextDocument::end() const
205 {
206   return mBlocks.end();
207 }
208 ///@endcond
209