1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include <QStack> |
41 | #include <QVector> |
42 | #include <QPainter> |
43 | #include <QTextLayout> |
44 | #include <QDebug> |
45 | #include <qmath.h> |
46 | #include "qquickstyledtext_p.h" |
47 | #include <QQmlContext> |
48 | |
49 | Q_LOGGING_CATEGORY(lcStyledText, "qt.quick.styledtext" ) |
50 | |
51 | /* |
52 | QQuickStyledText supports few tags: |
53 | |
54 | <b></b> - bold |
55 | <del></del> - strike out (removed content) |
56 | <s></s> - strike out (no longer accurate or no longer relevant content) |
57 | <strong></strong> - bold |
58 | <i></i> - italic |
59 | <br> - new line |
60 | <p> - paragraph |
61 | <u> - underlined text |
62 | <font color="color_name" size="1-7"></font> |
63 | <h1> to <h6> - headers |
64 | <a href=""> - anchor |
65 | <ol type="">, <ul type=""> and <li> - ordered and unordered lists |
66 | <pre></pre> - preformated |
67 | <img src=""> - images |
68 | |
69 | The opening and closing tags must be correctly nested. |
70 | */ |
71 | |
72 | QT_BEGIN_NAMESPACE |
73 | |
74 | Q_GUI_EXPORT int qt_defaultDpi(); |
75 | |
76 | class QQuickStyledTextPrivate |
77 | { |
78 | public: |
79 | enum ListType { Ordered, Unordered }; |
80 | enum ListFormat { Bullet, Disc, Square, Decimal, LowerAlpha, UpperAlpha, LowerRoman, UpperRoman }; |
81 | |
82 | struct List { |
83 | int level; |
84 | ListType type; |
85 | ListFormat format; |
86 | }; |
87 | |
88 | QQuickStyledTextPrivate(const QString &t, QTextLayout &l, |
89 | QList<QQuickStyledTextImgTag*> &imgTags, |
90 | const QUrl &baseUrl, |
91 | QQmlContext *context, |
92 | bool preloadImages, |
93 | bool *fontSizeModified) |
94 | : text(t), layout(l), imgTags(&imgTags), baseFont(layout.font()), baseUrl(baseUrl), |
95 | fontSizeModified(fontSizeModified), context(context), preloadImages(preloadImages) |
96 | { |
97 | } |
98 | |
99 | void parse(); |
100 | void appendText(const QString &textIn, int start, int length, QString &textOut); |
101 | bool parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format); |
102 | bool parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut); |
103 | void parseEntity(const QChar *&ch, const QString &textIn, QString &textOut); |
104 | bool parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format); |
105 | bool parseOrderedListAttributes(const QChar *&ch, const QString &textIn); |
106 | bool parseUnorderedListAttributes(const QChar *&ch, const QString &textIn); |
107 | bool parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format); |
108 | void parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut); |
109 | QPair<QStringRef,QStringRef> parseAttribute(const QChar *&ch, const QString &textIn); |
110 | QStringRef parseValue(const QChar *&ch, const QString &textIn); |
111 | void setFontSize(int size, QTextCharFormat &format); |
112 | |
113 | inline void skipSpace(const QChar *&ch) { |
114 | while (ch->isSpace() && !ch->isNull()) |
115 | ++ch; |
116 | } |
117 | |
118 | static QString toAlpha(int value, bool upper); |
119 | static QString toRoman(int value, bool upper); |
120 | |
121 | QString text; |
122 | QTextLayout &layout; |
123 | QList<QQuickStyledTextImgTag*> *imgTags; |
124 | QFont baseFont; |
125 | QStack<List> listStack; |
126 | QUrl baseUrl; |
127 | bool *fontSizeModified; |
128 | QQmlContext *context; |
129 | int nbImages = 0; |
130 | bool hasNewLine = true; |
131 | bool updateImagePositions = false; |
132 | bool preFormat = false; |
133 | bool prependSpace = false; |
134 | bool hasSpace = true; |
135 | bool preloadImages; |
136 | |
137 | static const QChar lessThan; |
138 | static const QChar greaterThan; |
139 | static const QChar equals; |
140 | static const QChar singleQuote; |
141 | static const QChar doubleQuote; |
142 | static const QChar slash; |
143 | static const QChar ampersand; |
144 | static const QChar bullet; |
145 | static const QChar disc; |
146 | static const QChar square; |
147 | static const QChar lineFeed; |
148 | static const QChar space; |
149 | static const int tabsize = 6; |
150 | }; |
151 | |
152 | const QChar QQuickStyledTextPrivate::lessThan(QLatin1Char('<')); |
153 | const QChar QQuickStyledTextPrivate::greaterThan(QLatin1Char('>')); |
154 | const QChar QQuickStyledTextPrivate::equals(QLatin1Char('=')); |
155 | const QChar QQuickStyledTextPrivate::singleQuote(QLatin1Char('\'')); |
156 | const QChar QQuickStyledTextPrivate::doubleQuote(QLatin1Char('\"')); |
157 | const QChar QQuickStyledTextPrivate::slash(QLatin1Char('/')); |
158 | const QChar QQuickStyledTextPrivate::ampersand(QLatin1Char('&')); |
159 | const QChar QQuickStyledTextPrivate::bullet(0x2022); |
160 | const QChar QQuickStyledTextPrivate::disc(0x25e6); |
161 | const QChar QQuickStyledTextPrivate::square(0x25a1); |
162 | const QChar QQuickStyledTextPrivate::lineFeed(QLatin1Char('\n')); |
163 | const QChar QQuickStyledTextPrivate::space(QLatin1Char(' ')); |
164 | |
165 | QQuickStyledText::QQuickStyledText(const QString &string, QTextLayout &layout, |
166 | QList<QQuickStyledTextImgTag*> &imgTags, |
167 | const QUrl &baseUrl, |
168 | QQmlContext *context, |
169 | bool preloadImages, |
170 | bool *fontSizeModified) |
171 | : d(new QQuickStyledTextPrivate(string, layout, imgTags, baseUrl, context, preloadImages, fontSizeModified)) |
172 | { |
173 | } |
174 | |
175 | QQuickStyledText::~QQuickStyledText() |
176 | { |
177 | delete d; |
178 | } |
179 | |
180 | void QQuickStyledText::parse(const QString &string, QTextLayout &layout, |
181 | QList<QQuickStyledTextImgTag*> &imgTags, |
182 | const QUrl &baseUrl, |
183 | QQmlContext *context, |
184 | bool preloadImages, |
185 | bool *fontSizeModified) |
186 | { |
187 | if (string.isEmpty()) |
188 | return; |
189 | QQuickStyledText styledText(string, layout, imgTags, baseUrl, context, preloadImages, fontSizeModified); |
190 | styledText.d->parse(); |
191 | } |
192 | |
193 | void QQuickStyledTextPrivate::parse() |
194 | { |
195 | QVector<QTextLayout::FormatRange> ranges; |
196 | QStack<QTextCharFormat> formatStack; |
197 | |
198 | QString drawText; |
199 | drawText.reserve(asize: text.count()); |
200 | |
201 | updateImagePositions = !imgTags->isEmpty(); |
202 | |
203 | int textStart = 0; |
204 | int textLength = 0; |
205 | int rangeStart = 0; |
206 | bool formatChanged = false; |
207 | |
208 | const QChar *ch = text.constData(); |
209 | while (!ch->isNull()) { |
210 | if (*ch == lessThan) { |
211 | if (textLength) { |
212 | appendText(textIn: text, start: textStart, length: textLength, textOut&: drawText); |
213 | } else if (prependSpace) { |
214 | drawText.append(c: space); |
215 | prependSpace = false; |
216 | hasSpace = true; |
217 | } |
218 | |
219 | if (rangeStart != drawText.length() && formatStack.count()) { |
220 | if (formatChanged) { |
221 | QTextLayout::FormatRange formatRange; |
222 | formatRange.format = formatStack.top(); |
223 | formatRange.start = rangeStart; |
224 | formatRange.length = drawText.length() - rangeStart; |
225 | ranges.append(t: formatRange); |
226 | formatChanged = false; |
227 | } else if (ranges.count()) { |
228 | ranges.last().length += drawText.length() - rangeStart; |
229 | } |
230 | } |
231 | rangeStart = drawText.length(); |
232 | ++ch; |
233 | if (*ch == slash) { |
234 | ++ch; |
235 | if (parseCloseTag(ch, textIn: text, textOut&: drawText)) { |
236 | if (formatStack.count()) { |
237 | formatChanged = true; |
238 | formatStack.pop(); |
239 | } |
240 | } |
241 | } else { |
242 | QTextCharFormat format; |
243 | if (formatStack.count()) |
244 | format = formatStack.top(); |
245 | if (parseTag(ch, textIn: text, textOut&: drawText, format)) { |
246 | formatChanged = true; |
247 | formatStack.push(t: format); |
248 | } |
249 | } |
250 | textStart = ch - text.constData() + 1; |
251 | textLength = 0; |
252 | } else if (*ch == ampersand) { |
253 | ++ch; |
254 | appendText(textIn: text, start: textStart, length: textLength, textOut&: drawText); |
255 | parseEntity(ch, textIn: text, textOut&: drawText); |
256 | textStart = ch - text.constData() + 1; |
257 | textLength = 0; |
258 | } else if (ch->isSpace()) { |
259 | if (textLength) |
260 | appendText(textIn: text, start: textStart, length: textLength, textOut&: drawText); |
261 | if (!preFormat) { |
262 | prependSpace = !hasSpace; |
263 | for (const QChar *n = ch + 1; !n->isNull() && n->isSpace(); ++n) |
264 | ch = n; |
265 | hasNewLine = false; |
266 | } else if (*ch == lineFeed) { |
267 | drawText.append(c: QChar(QChar::LineSeparator)); |
268 | hasNewLine = true; |
269 | } else { |
270 | drawText.append(c: QChar(QChar::Nbsp)); |
271 | hasNewLine = false; |
272 | } |
273 | textStart = ch - text.constData() + 1; |
274 | textLength = 0; |
275 | } else { |
276 | ++textLength; |
277 | } |
278 | if (!ch->isNull()) |
279 | ++ch; |
280 | } |
281 | if (textLength) |
282 | appendText(textIn: text, start: textStart, length: textLength, textOut&: drawText); |
283 | if (rangeStart != drawText.length() && formatStack.count()) { |
284 | if (formatChanged) { |
285 | QTextLayout::FormatRange formatRange; |
286 | formatRange.format = formatStack.top(); |
287 | formatRange.start = rangeStart; |
288 | formatRange.length = drawText.length() - rangeStart; |
289 | ranges.append(t: formatRange); |
290 | } else if (ranges.count()) { |
291 | ranges.last().length += drawText.length() - rangeStart; |
292 | } |
293 | } |
294 | |
295 | layout.setText(drawText); |
296 | layout.setFormats(ranges); |
297 | } |
298 | |
299 | void QQuickStyledTextPrivate::appendText(const QString &textIn, int start, int length, QString &textOut) |
300 | { |
301 | if (prependSpace) |
302 | textOut.append(c: space); |
303 | textOut.append(s: QStringRef(&textIn, start, length)); |
304 | prependSpace = false; |
305 | hasSpace = false; |
306 | hasNewLine = false; |
307 | } |
308 | |
309 | // |
310 | // Calculates and sets the correct font size in points |
311 | // depending on the size multiplier and base font. |
312 | // |
313 | void QQuickStyledTextPrivate::setFontSize(int size, QTextCharFormat &format) |
314 | { |
315 | static const qreal scaling[] = { 0.7, 0.8, 1.0, 1.2, 1.5, 2.0, 2.4 }; |
316 | if (baseFont.pointSizeF() != -1) |
317 | format.setFontPointSize(baseFont.pointSize() * scaling[size - 1]); |
318 | else |
319 | format.setFontPointSize(baseFont.pixelSize() * qreal(72.) / qreal(qt_defaultDpi()) * scaling[size - 1]); |
320 | *fontSizeModified = true; |
321 | } |
322 | |
323 | bool QQuickStyledTextPrivate::parseTag(const QChar *&ch, const QString &textIn, QString &textOut, QTextCharFormat &format) |
324 | { |
325 | skipSpace(ch); |
326 | |
327 | int tagStart = ch - textIn.constData(); |
328 | int tagLength = 0; |
329 | while (!ch->isNull()) { |
330 | if (*ch == greaterThan) { |
331 | if (tagLength == 0) |
332 | return false; |
333 | QStringRef tag(&textIn, tagStart, tagLength); |
334 | const QChar char0 = tag.at(i: 0); |
335 | if (char0 == QLatin1Char('b')) { |
336 | if (tagLength == 1) { |
337 | format.setFontWeight(QFont::Bold); |
338 | return true; |
339 | } else if (tagLength == 2 && tag.at(i: 1) == QLatin1Char('r')) { |
340 | textOut.append(c: QChar(QChar::LineSeparator)); |
341 | hasSpace = true; |
342 | prependSpace = false; |
343 | return false; |
344 | } |
345 | } else if (char0 == QLatin1Char('i')) { |
346 | if (tagLength == 1) { |
347 | format.setFontItalic(true); |
348 | return true; |
349 | } |
350 | } else if (char0 == QLatin1Char('p')) { |
351 | if (tagLength == 1) { |
352 | if (!hasNewLine) |
353 | textOut.append(c: QChar::LineSeparator); |
354 | hasSpace = true; |
355 | prependSpace = false; |
356 | } else if (tag == QLatin1String("pre" )) { |
357 | preFormat = true; |
358 | if (!hasNewLine) |
359 | textOut.append(c: QChar::LineSeparator); |
360 | format.setFontFamily(QString::fromLatin1(str: "Courier New,courier" )); |
361 | format.setFontFixedPitch(true); |
362 | return true; |
363 | } |
364 | } else if (char0 == QLatin1Char('u')) { |
365 | if (tagLength == 1) { |
366 | format.setFontUnderline(true); |
367 | return true; |
368 | } else if (tag == QLatin1String("ul" )) { |
369 | List listItem; |
370 | listItem.level = 0; |
371 | listItem.type = Unordered; |
372 | listItem.format = Bullet; |
373 | listStack.push(t: listItem); |
374 | } |
375 | } else if (char0 == QLatin1Char('h') && tagLength == 2) { |
376 | int level = tag.at(i: 1).digitValue(); |
377 | if (level >= 1 && level <= 6) { |
378 | if (!hasNewLine) |
379 | textOut.append(c: QChar::LineSeparator); |
380 | hasSpace = true; |
381 | prependSpace = false; |
382 | setFontSize(size: 7 - level, format); |
383 | format.setFontWeight(QFont::Bold); |
384 | return true; |
385 | } |
386 | } else if (char0 == QLatin1Char('s')) { |
387 | if (tagLength == 1) { |
388 | format.setFontStrikeOut(true); |
389 | return true; |
390 | } else if (tag == QLatin1String("strong" )) { |
391 | format.setFontWeight(QFont::Bold); |
392 | return true; |
393 | } |
394 | } else if (tag == QLatin1String("del" )) { |
395 | format.setFontStrikeOut(true); |
396 | return true; |
397 | } else if (tag == QLatin1String("ol" )) { |
398 | List listItem; |
399 | listItem.level = 0; |
400 | listItem.type = Ordered; |
401 | listItem.format = Decimal; |
402 | listStack.push(t: listItem); |
403 | } else if (tag == QLatin1String("li" )) { |
404 | if (!hasNewLine) |
405 | textOut.append(c: QChar(QChar::LineSeparator)); |
406 | if (!listStack.isEmpty()) { |
407 | int count = ++listStack.top().level; |
408 | for (int i = 0; i < listStack.size(); ++i) |
409 | textOut += QString(tabsize, QChar::Nbsp); |
410 | switch (listStack.top().format) { |
411 | case Decimal: |
412 | textOut += QString::number(count) % QLatin1Char('.'); |
413 | break; |
414 | case LowerAlpha: |
415 | textOut += toAlpha(value: count, upper: false) % QLatin1Char('.'); |
416 | break; |
417 | case UpperAlpha: |
418 | textOut += toAlpha(value: count, upper: true) % QLatin1Char('.'); |
419 | break; |
420 | case LowerRoman: |
421 | textOut += toRoman(value: count, upper: false) % QLatin1Char('.'); |
422 | break; |
423 | case UpperRoman: |
424 | textOut += toRoman(value: count, upper: true) % QLatin1Char('.'); |
425 | break; |
426 | case Bullet: |
427 | textOut += bullet; |
428 | break; |
429 | case Disc: |
430 | textOut += disc; |
431 | break; |
432 | case Square: |
433 | textOut += square; |
434 | break; |
435 | } |
436 | textOut += QString(2, QChar::Nbsp); |
437 | } |
438 | } |
439 | return false; |
440 | } else if (ch->isSpace()) { |
441 | // may have params. |
442 | QStringRef tag(&textIn, tagStart, tagLength); |
443 | if (tag == QLatin1String("font" )) |
444 | return parseFontAttributes(ch, textIn, format); |
445 | if (tag == QLatin1String("ol" )) { |
446 | parseOrderedListAttributes(ch, textIn); |
447 | return false; // doesn't modify format |
448 | } |
449 | if (tag == QLatin1String("ul" )) { |
450 | parseUnorderedListAttributes(ch, textIn); |
451 | return false; // doesn't modify format |
452 | } |
453 | if (tag == QLatin1String("a" )) { |
454 | return parseAnchorAttributes(ch, textIn, format); |
455 | } |
456 | if (tag == QLatin1String("img" )) { |
457 | parseImageAttributes(ch, textIn, textOut); |
458 | return false; |
459 | } |
460 | if (*ch == greaterThan || ch->isNull()) |
461 | continue; |
462 | } else if (*ch != slash) { |
463 | tagLength++; |
464 | } |
465 | ++ch; |
466 | } |
467 | return false; |
468 | } |
469 | |
470 | bool QQuickStyledTextPrivate::parseCloseTag(const QChar *&ch, const QString &textIn, QString &textOut) |
471 | { |
472 | skipSpace(ch); |
473 | |
474 | int tagStart = ch - textIn.constData(); |
475 | int tagLength = 0; |
476 | while (!ch->isNull()) { |
477 | if (*ch == greaterThan) { |
478 | if (tagLength == 0) |
479 | return false; |
480 | QStringRef tag(&textIn, tagStart, tagLength); |
481 | const QChar char0 = tag.at(i: 0); |
482 | hasNewLine = false; |
483 | if (char0 == QLatin1Char('b')) { |
484 | if (tagLength == 1) |
485 | return true; |
486 | else if (tag.at(i: 1) == QLatin1Char('r') && tagLength == 2) |
487 | return false; |
488 | } else if (char0 == QLatin1Char('i')) { |
489 | if (tagLength == 1) |
490 | return true; |
491 | } else if (char0 == QLatin1Char('a')) { |
492 | if (tagLength == 1) |
493 | return true; |
494 | } else if (char0 == QLatin1Char('p')) { |
495 | if (tagLength == 1) { |
496 | textOut.append(c: QChar::LineSeparator); |
497 | hasNewLine = true; |
498 | hasSpace = true; |
499 | return false; |
500 | } else if (tag == QLatin1String("pre" )) { |
501 | preFormat = false; |
502 | if (!hasNewLine) |
503 | textOut.append(c: QChar::LineSeparator); |
504 | hasNewLine = true; |
505 | hasSpace = true; |
506 | return true; |
507 | } |
508 | } else if (char0 == QLatin1Char('u')) { |
509 | if (tagLength == 1) |
510 | return true; |
511 | else if (tag == QLatin1String("ul" )) { |
512 | if (!listStack.isEmpty()) { |
513 | listStack.pop(); |
514 | if (!listStack.count()) |
515 | textOut.append(c: QChar::LineSeparator); |
516 | } |
517 | return false; |
518 | } |
519 | } else if (char0 == QLatin1Char('h') && tagLength == 2) { |
520 | textOut.append(c: QChar::LineSeparator); |
521 | hasNewLine = true; |
522 | hasSpace = true; |
523 | return true; |
524 | } else if (tag == QLatin1String("font" )) { |
525 | return true; |
526 | } else if (char0 == QLatin1Char('s')) { |
527 | if (tagLength == 1) { |
528 | return true; |
529 | } else if (tag == QLatin1String("strong" )) { |
530 | return true; |
531 | } |
532 | } else if (tag == QLatin1String("del" )) { |
533 | return true; |
534 | } else if (tag == QLatin1String("ol" )) { |
535 | if (!listStack.isEmpty()) { |
536 | listStack.pop(); |
537 | if (!listStack.count()) |
538 | textOut.append(c: QChar::LineSeparator); |
539 | } |
540 | return false; |
541 | } else if (tag == QLatin1String("li" )) { |
542 | return false; |
543 | } |
544 | return false; |
545 | } else if (!ch->isSpace()){ |
546 | tagLength++; |
547 | } |
548 | ++ch; |
549 | } |
550 | |
551 | return false; |
552 | } |
553 | |
554 | void QQuickStyledTextPrivate::parseEntity(const QChar *&ch, const QString &textIn, QString &textOut) |
555 | { |
556 | int entityStart = ch - textIn.constData(); |
557 | int entityLength = 0; |
558 | while (!ch->isNull()) { |
559 | if (*ch == QLatin1Char(';')) { |
560 | QStringRef entity(&textIn, entityStart, entityLength); |
561 | if (entity == QLatin1String("gt" )) |
562 | textOut += QChar(62); |
563 | else if (entity == QLatin1String("lt" )) |
564 | textOut += QChar(60); |
565 | else if (entity == QLatin1String("amp" )) |
566 | textOut += QChar(38); |
567 | else if (entity == QLatin1String("apos" )) |
568 | textOut += QChar(39); |
569 | else if (entity == QLatin1String("quot" )) |
570 | textOut += QChar(34); |
571 | else if (entity == QLatin1String("nbsp" )) |
572 | textOut += QChar(QChar::Nbsp); |
573 | else |
574 | qCWarning(lcStyledText) << "StyledText doesn't support entity" << entity; |
575 | return; |
576 | } else if (*ch == QLatin1Char(' ')) { |
577 | QStringRef entity(&textIn, entityStart - 1, entityLength + 1); |
578 | textOut += entity + *ch; |
579 | return; |
580 | } |
581 | ++entityLength; |
582 | ++ch; |
583 | } |
584 | } |
585 | |
586 | bool QQuickStyledTextPrivate::parseFontAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format) |
587 | { |
588 | bool valid = false; |
589 | QPair<QStringRef,QStringRef> attr; |
590 | do { |
591 | attr = parseAttribute(ch, textIn); |
592 | if (attr.first == QLatin1String("color" )) { |
593 | valid = true; |
594 | format.setForeground(QColor(attr.second.toString())); |
595 | } else if (attr.first == QLatin1String("size" )) { |
596 | valid = true; |
597 | int size = attr.second.toString().toInt(); |
598 | if (attr.second.at(i: 0) == QLatin1Char('-') || attr.second.at(i: 0) == QLatin1Char('+')) |
599 | size += 3; |
600 | if (size >= 1 && size <= 7) |
601 | setFontSize(size, format); |
602 | } |
603 | } while (!ch->isNull() && !attr.first.isEmpty()); |
604 | |
605 | return valid; |
606 | } |
607 | |
608 | bool QQuickStyledTextPrivate::parseOrderedListAttributes(const QChar *&ch, const QString &textIn) |
609 | { |
610 | bool valid = false; |
611 | |
612 | List listItem; |
613 | listItem.level = 0; |
614 | listItem.type = Ordered; |
615 | listItem.format = Decimal; |
616 | |
617 | QPair<QStringRef,QStringRef> attr; |
618 | do { |
619 | attr = parseAttribute(ch, textIn); |
620 | if (attr.first == QLatin1String("type" )) { |
621 | valid = true; |
622 | if (attr.second == QLatin1String("a" )) |
623 | listItem.format = LowerAlpha; |
624 | else if (attr.second == QLatin1String("A" )) |
625 | listItem.format = UpperAlpha; |
626 | else if (attr.second == QLatin1String("i" )) |
627 | listItem.format = LowerRoman; |
628 | else if (attr.second == QLatin1String("I" )) |
629 | listItem.format = UpperRoman; |
630 | } |
631 | } while (!ch->isNull() && !attr.first.isEmpty()); |
632 | |
633 | listStack.push(t: listItem); |
634 | return valid; |
635 | } |
636 | |
637 | bool QQuickStyledTextPrivate::parseUnorderedListAttributes(const QChar *&ch, const QString &textIn) |
638 | { |
639 | bool valid = false; |
640 | |
641 | List listItem; |
642 | listItem.level = 0; |
643 | listItem.type = Unordered; |
644 | listItem.format = Bullet; |
645 | |
646 | QPair<QStringRef,QStringRef> attr; |
647 | do { |
648 | attr = parseAttribute(ch, textIn); |
649 | if (attr.first == QLatin1String("type" )) { |
650 | valid = true; |
651 | if (attr.second == QLatin1String("disc" )) |
652 | listItem.format = Disc; |
653 | else if (attr.second == QLatin1String("square" )) |
654 | listItem.format = Square; |
655 | } |
656 | } while (!ch->isNull() && !attr.first.isEmpty()); |
657 | |
658 | listStack.push(t: listItem); |
659 | return valid; |
660 | } |
661 | |
662 | bool QQuickStyledTextPrivate::parseAnchorAttributes(const QChar *&ch, const QString &textIn, QTextCharFormat &format) |
663 | { |
664 | bool valid = false; |
665 | |
666 | QPair<QStringRef,QStringRef> attr; |
667 | do { |
668 | attr = parseAttribute(ch, textIn); |
669 | if (attr.first == QLatin1String("href" )) { |
670 | format.setAnchorHref(attr.second.toString()); |
671 | format.setAnchor(true); |
672 | format.setFontUnderline(true); |
673 | valid = true; |
674 | } |
675 | } while (!ch->isNull() && !attr.first.isEmpty()); |
676 | |
677 | return valid; |
678 | } |
679 | |
680 | void QQuickStyledTextPrivate::parseImageAttributes(const QChar *&ch, const QString &textIn, QString &textOut) |
681 | { |
682 | qreal imgWidth = 0.0; |
683 | QFontMetricsF fm(layout.font()); |
684 | const qreal spaceWidth = fm.horizontalAdvance(QChar::Nbsp); |
685 | const bool trailingSpace = textOut.endsWith(c: space); |
686 | |
687 | if (!updateImagePositions) { |
688 | QQuickStyledTextImgTag *image = new QQuickStyledTextImgTag; |
689 | image->position = textOut.length() + (trailingSpace ? 0 : 1); |
690 | |
691 | QPair<QStringRef,QStringRef> attr; |
692 | do { |
693 | attr = parseAttribute(ch, textIn); |
694 | if (attr.first == QLatin1String("src" )) { |
695 | image->url = QUrl(attr.second.toString()); |
696 | } else if (attr.first == QLatin1String("width" )) { |
697 | image->size.setWidth(attr.second.toString().toInt()); |
698 | } else if (attr.first == QLatin1String("height" )) { |
699 | image->size.setHeight(attr.second.toString().toInt()); |
700 | } else if (attr.first == QLatin1String("align" )) { |
701 | if (attr.second.toString() == QLatin1String("top" )) { |
702 | image->align = QQuickStyledTextImgTag::Top; |
703 | } else if (attr.second.toString() == QLatin1String("middle" )) { |
704 | image->align = QQuickStyledTextImgTag::Middle; |
705 | } |
706 | } |
707 | } while (!ch->isNull() && !attr.first.isEmpty()); |
708 | |
709 | if (preloadImages && !image->size.isValid()) { |
710 | // if we don't know its size but the image is a local image, |
711 | // we load it in the pixmap cache and save its implicit size |
712 | // to avoid a relayout later on. |
713 | QUrl url = baseUrl.resolved(relative: image->url); |
714 | if (url.isLocalFile()) { |
715 | image->pix = new QQuickPixmap(context->engine(), url, QRect(), image->size); |
716 | if (image->pix && image->pix->isReady()) { |
717 | image->size = image->pix->implicitSize(); |
718 | } else { |
719 | delete image->pix; |
720 | image->pix = nullptr; |
721 | } |
722 | } |
723 | } |
724 | |
725 | imgWidth = image->size.width(); |
726 | image->offset = -std::fmod(x: imgWidth, y: spaceWidth) / 2.0; |
727 | imgTags->append(t: image); |
728 | |
729 | } else { |
730 | // if we already have a list of img tags for this text |
731 | // we only want to update the positions of these tags. |
732 | QQuickStyledTextImgTag *image = imgTags->value(i: nbImages); |
733 | image->position = textOut.length() + (trailingSpace ? 0 : 1); |
734 | imgWidth = image->size.width(); |
735 | image->offset = -std::fmod(x: imgWidth, y: spaceWidth) / 2.0; |
736 | QPair<QStringRef,QStringRef> attr; |
737 | do { |
738 | attr = parseAttribute(ch, textIn); |
739 | } while (!ch->isNull() && !attr.first.isEmpty()); |
740 | nbImages++; |
741 | } |
742 | |
743 | QString padding(qFloor(v: imgWidth / spaceWidth), QChar::Nbsp); |
744 | if (!trailingSpace) |
745 | textOut += QLatin1Char(' '); |
746 | textOut += padding + QLatin1Char(' '); |
747 | } |
748 | |
749 | QPair<QStringRef,QStringRef> QQuickStyledTextPrivate::parseAttribute(const QChar *&ch, const QString &textIn) |
750 | { |
751 | skipSpace(ch); |
752 | |
753 | int attrStart = ch - textIn.constData(); |
754 | int attrLength = 0; |
755 | while (!ch->isNull()) { |
756 | if (*ch == greaterThan) { |
757 | break; |
758 | } else if (*ch == equals) { |
759 | ++ch; |
760 | if (*ch != singleQuote && *ch != doubleQuote) { |
761 | while (*ch != greaterThan && !ch->isNull()) |
762 | ++ch; |
763 | break; |
764 | } |
765 | ++ch; |
766 | if (!attrLength) |
767 | break; |
768 | QStringRef attr(&textIn, attrStart, attrLength); |
769 | QStringRef val = parseValue(ch, textIn); |
770 | if (!val.isEmpty()) |
771 | return QPair<QStringRef,QStringRef>(attr,val); |
772 | break; |
773 | } else { |
774 | ++attrLength; |
775 | } |
776 | ++ch; |
777 | } |
778 | |
779 | return QPair<QStringRef,QStringRef>(); |
780 | } |
781 | |
782 | QStringRef QQuickStyledTextPrivate::parseValue(const QChar *&ch, const QString &textIn) |
783 | { |
784 | int valStart = ch - textIn.constData(); |
785 | int valLength = 0; |
786 | while (*ch != singleQuote && *ch != doubleQuote && !ch->isNull()) { |
787 | ++valLength; |
788 | ++ch; |
789 | } |
790 | if (ch->isNull()) |
791 | return QStringRef(); |
792 | ++ch; // skip quote |
793 | |
794 | return QStringRef(&textIn, valStart, valLength); |
795 | } |
796 | |
797 | QString QQuickStyledTextPrivate::toAlpha(int value, bool upper) |
798 | { |
799 | const char baseChar = upper ? 'A' : 'a'; |
800 | |
801 | QString result; |
802 | int c = value; |
803 | while (c > 0) { |
804 | c--; |
805 | result.prepend(c: QChar(baseChar + (c % 26))); |
806 | c /= 26; |
807 | } |
808 | return result; |
809 | } |
810 | |
811 | QString QQuickStyledTextPrivate::toRoman(int value, bool upper) |
812 | { |
813 | QString result = QLatin1String("?" ); |
814 | // works for up to 4999 items |
815 | if (value < 5000) { |
816 | QByteArray romanNumeral; |
817 | |
818 | static const char romanSymbolsLower[] = "iiivixxxlxcccdcmmmm" ; |
819 | static const char romanSymbolsUpper[] = "IIIVIXXXLXCCCDCMMMM" ; |
820 | QByteArray romanSymbols; |
821 | if (!upper) |
822 | romanSymbols = QByteArray::fromRawData(romanSymbolsLower, size: sizeof(romanSymbolsLower)); |
823 | else |
824 | romanSymbols = QByteArray::fromRawData(romanSymbolsUpper, size: sizeof(romanSymbolsUpper)); |
825 | |
826 | int c[] = { 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000 }; |
827 | int n = value; |
828 | for (int i = 12; i >= 0; n %= c[i], i--) { |
829 | int q = n / c[i]; |
830 | if (q > 0) { |
831 | int startDigit = i + (i + 3) / 4; |
832 | int numDigits; |
833 | if (i % 4) { |
834 | if ((i - 2) % 4) |
835 | numDigits = 2; |
836 | else |
837 | numDigits = 1; |
838 | } |
839 | else |
840 | numDigits = q; |
841 | romanNumeral.append(a: romanSymbols.mid(index: startDigit, len: numDigits)); |
842 | } |
843 | } |
844 | result = QString::fromLatin1(str: romanNumeral); |
845 | } |
846 | return result; |
847 | } |
848 | |
849 | QT_END_NAMESPACE |
850 | |