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