QGIS API Documentation 3.38.0-Grenoble (exported)
Loading...
Searching...
No Matches
qgstextdocument.cpp
Go to the documentation of this file.
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
22#include <QTextDocument>
23#include <QTextBlock>
24
25
27
29
31{
32 mBlocks.append( block );
33}
34
36{
37 mBlocks.append( QgsTextBlock( fragment ) );
38}
39
41{
42 QgsTextDocument document;
43 document.reserve( lines.size() );
44 for ( const QString &line : lines )
45 {
46 document.append( QgsTextBlock::fromPlainText( line ) );
47 }
48 return document;
49}
50
51// Note -- must start and end with spaces, so that a tab character within
52// a html or css tag doesn't mess things up. Instead, Qt will just silently
53// ignore html attributes it doesn't know about, like this replacement string
54#define TAB_REPLACEMENT_MARKER " ignore_me_i_am_a_tab "
55
56QgsTextDocument QgsTextDocument::fromHtml( const QStringList &lines )
57{
58 QgsTextDocument document;
59
60 document.reserve( lines.size() );
61
62 for ( const QString &l : std::as_const( lines ) )
63 {
64 QString line = l;
65 // QTextDocument is a very heavy way of parsing HTML + css (it's heavily geared toward an editable text document,
66 // and includes a LOT of calculations we don't need, when all we're after is a HTML + CSS style parser).
67 // TODO - try to find an alternative library we can use here
68
69 QTextDocument sourceDoc;
70
71 // QTextDocument will replace tab characters with a space. We need to hack around this
72 // by first replacing it with a string which QTextDocument won't mess with, and then
73 // handle these markers as tab characters in the parsed HTML document.
74 line.replace( QString( '\t' ), QStringLiteral( TAB_REPLACEMENT_MARKER ) );
75
76 sourceDoc.setHtml( line );
77
78 QTextBlock sourceBlock = sourceDoc.firstBlock();
79
80 while ( true )
81 {
82 auto it = sourceBlock.begin();
83 QgsTextBlock block;
84 while ( !it.atEnd() )
85 {
86 const QTextFragment fragment = it.fragment();
87 if ( fragment.isValid() )
88 {
89 // Search for line breaks in the fragment
90 const QString fragmentText = fragment.text();
91 if ( fragmentText.contains( QStringLiteral( "\u2028" ) ) )
92 {
93 // Split fragment text into lines
94 const QStringList splitLines = fragmentText.split( QStringLiteral( "\u2028" ), Qt::SplitBehaviorFlags::SkipEmptyParts );
95
96 for ( const QString &splitLine : std::as_const( splitLines ) )
97 {
98 const QgsTextCharacterFormat *previousFormat = nullptr;
99
100 // If the splitLine is not the first, inherit style from previous fragment
101 if ( splitLine != splitLines.first() && document.size() > 0 )
102 {
103 previousFormat = &document.at( document.size() - 1 ).at( 0 ).characterFormat();
104 }
105
106 if ( splitLine.contains( QStringLiteral( TAB_REPLACEMENT_MARKER ) ) )
107 {
108 // split line by tab characters, each tab should be a
109 // fragment by itself
110 QgsTextFragment splitFragment( fragment );
111 if ( previousFormat )
112 {
113 // Apply overrides from previous fragment
114 QgsTextCharacterFormat newFormat { splitFragment.characterFormat() };
115 newFormat.overrideWith( *previousFormat );
116 splitFragment.setCharacterFormat( newFormat );
117 }
118
119 const QStringList tabSplit = splitLine.split( QStringLiteral( TAB_REPLACEMENT_MARKER ) );
120 int index = 0;
121 for ( const QString &part : tabSplit )
122 {
123 if ( !part.isEmpty() )
124 {
125 splitFragment.setText( part );
126 block.append( splitFragment );
127 }
128 if ( index != tabSplit.size() - 1 )
129 {
130 block.append( QgsTextFragment( QString( '\t' ) ) );
131 }
132 index++;
133 }
134 }
135 else
136 {
137 QgsTextFragment splitFragment( fragment );
138 splitFragment.setText( splitLine );
139
140 if ( previousFormat )
141 {
142 // Apply overrides from previous fragment
143 QgsTextCharacterFormat newFormat { splitFragment.characterFormat() };
144 newFormat.overrideWith( *previousFormat );
145 splitFragment.setCharacterFormat( newFormat );
146 }
147
148 block.append( splitFragment );
149 }
150
151 document.append( block );
152 block = QgsTextBlock();
153 }
154 }
155 else if ( fragmentText.contains( QStringLiteral( TAB_REPLACEMENT_MARKER ) ) )
156 {
157 // split line by tab characters, each tab should be a
158 // fragment by itself
159 QgsTextFragment tmpFragment( fragment );
160 const QStringList tabSplit = fragmentText.split( QStringLiteral( TAB_REPLACEMENT_MARKER ) );
161 int index = 0;
162 for ( const QString &part : tabSplit )
163 {
164 if ( !part.isEmpty() )
165 {
166 tmpFragment.setText( part );
167 block.append( tmpFragment );
168 }
169 if ( index != tabSplit.size() - 1 )
170 {
171 block.append( QgsTextFragment( QString( '\t' ) ) );
172 }
173 index++;
174 }
175 }
176 else
177 {
178 block.append( QgsTextFragment( fragment ) );
179 }
180 }
181 it++;
182 }
183
184 if ( !block.empty() )
185 document.append( block );
186
187 sourceBlock = sourceBlock.next();
188 if ( !sourceBlock.isValid() )
189 break;
190 }
191 }
192
193 return document;
194}
195
197{
198 mBlocks.append( block );
199}
200
202{
203 mBlocks.push_back( block );
204}
205
207{
208 mBlocks.reserve( count );
209}
210
212{
213 return mBlocks.at( i );
214}
215
217{
218 return mBlocks[i];
219}
220
222{
223 return mBlocks.size();
224}
225
227{
228 QStringList textLines;
229 textLines.reserve( mBlocks.size() );
230 for ( const QgsTextBlock &block : mBlocks )
231 {
232 QString line;
233 for ( const QgsTextFragment &fragment : block )
234 {
235 line.append( fragment.text() );
236 }
237 textLines << line;
238 }
239 return textLines;
240}
241
242void QgsTextDocument::splitLines( const QString &wrapCharacter, int autoWrapLength, bool useMaxLineLengthWhenAutoWrapping )
243{
244 const QVector< QgsTextBlock > prevBlocks = mBlocks;
245 mBlocks.clear();
246 mBlocks.reserve( prevBlocks.size() );
247 for ( const QgsTextBlock &block : prevBlocks )
248 {
249 QgsTextBlock destinationBlock;
250 for ( const QgsTextFragment &fragment : block )
251 {
252 QStringList thisParts;
253 if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) )
254 {
255 //wrap on both the wrapchr and new line characters
256 const QStringList lines = fragment.text().split( wrapCharacter );
257 for ( const QString &line : lines )
258 {
259 thisParts.append( line.split( '\n' ) );
260 }
261 }
262 else
263 {
264 thisParts = fragment.text().split( '\n' );
265 }
266
267 // apply auto wrapping to each manually created line
268 if ( autoWrapLength != 0 )
269 {
270 QStringList autoWrappedLines;
271 autoWrappedLines.reserve( thisParts.count() );
272 for ( const QString &line : std::as_const( thisParts ) )
273 {
274 autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) );
275 }
276 thisParts = autoWrappedLines;
277 }
278
279 if ( thisParts.empty() )
280 continue;
281 else if ( thisParts.size() == 1 )
282 destinationBlock.append( fragment );
283 else
284 {
285 if ( !thisParts.at( 0 ).isEmpty() )
286 destinationBlock.append( QgsTextFragment( thisParts.at( 0 ), fragment.characterFormat() ) );
287
288 append( destinationBlock );
289 destinationBlock.clear();
290 for ( int i = 1 ; i < thisParts.size() - 1; ++i )
291 {
292 append( QgsTextBlock( QgsTextFragment( thisParts.at( i ), fragment.characterFormat() ) ) );
293 }
294 destinationBlock.append( QgsTextFragment( thisParts.at( thisParts.size() - 1 ), fragment.characterFormat() ) );
295 }
296 }
297 append( destinationBlock );
298 }
299}
300
302{
303 for ( QgsTextBlock &block : mBlocks )
304 {
305 block.applyCapitalization( capitalization );
306 }
307}
308
310QVector< QgsTextBlock >::const_iterator QgsTextDocument::begin() const
311{
312 return mBlocks.begin();
313}
314
315QVector< QgsTextBlock >::const_iterator QgsTextDocument::end() const
316{
317 return mBlocks.end();
318}
Capitalization
String capitalization options.
Definition qgis.h:2884
static QString wordWrap(const QString &string, int length, bool useMaxLineLength=true, const QString &customDelimiter=QString())
Automatically wraps a string by inserting new line characters at appropriate locations in the string.
Represents a block of text consisting of one or more QgsTextFragment objects.
void clear()
Clears the block, removing all its contents.
static QgsTextBlock fromPlainText(const QString &text, const QgsTextCharacterFormat &format=QgsTextCharacterFormat())
Constructor for QgsTextBlock consisting of a plain text, and optional character format.
void append(const QgsTextFragment &fragment)
Appends a fragment to the block.
bool empty() const
Returns true if the block is empty.
Stores information relating to individual character formatting.
void overrideWith(const QgsTextCharacterFormat &other)
Override all the default/unset properties of the current character format with the settings from anot...
Represents a document consisting of one or more QgsTextBlock objects.
void splitLines(const QString &wrapCharacter, int autoWrapLength=0, bool useMaxLineLengthWhenAutoWrapping=true)
Splits lines of text in the document to separate lines, using a specified wrap character (wrapCharact...
QgsTextBlock & operator[](int index)
Returns the block at the specified index.
const QgsTextBlock & at(int index) const
Returns the block at the specified index.
void reserve(int count)
Reserves the specified count of blocks for optimised block appending.
QStringList toPlainText() const
Returns a list of plain text lines of text representing the document.
int size() const
Returns the number of blocks in the document.
static QgsTextDocument fromHtml(const QStringList &lines)
Constructor for QgsTextDocument consisting of a set of HTML formatted lines.
static QgsTextDocument fromPlainText(const QStringList &lines)
Constructor for QgsTextDocument consisting of a set of plain text lines.
void append(const QgsTextBlock &block)
Appends a block to the document.
void applyCapitalization(Qgis::Capitalization capitalization)
Applies a capitalization style to the document's text.
Stores a fragment of text along with formatting overrides to be used when rendering the fragment.
void setText(const QString &text)
Sets the text content of the fragment.
void setCharacterFormat(const QgsTextCharacterFormat &format)
Sets the character format for the fragment.
const QgsTextCharacterFormat & characterFormat() const
Returns the character formatting for the fragment.
#define TAB_REPLACEMENT_MARKER