Cutelee 6.1.0
markupdirector.cpp
1/*
2 This file is part of the Cutelee template system.
3
4 Copyright (c) 2008 Stephen Kelly <steveire@gmail.com>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either version
9 2.1 of the Licence, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library. If not, see <http://www.gnu.org/licenses/>.
18
19*/
20
21#include "markupdirector.h"
22#include "markupdirector_p.h"
23
24#include "abstractmarkupbuilder.h"
25
26#include <QtCore/QFlags>
27#include <QtCore/QMap>
28#include <QtCore/QStack>
29#include <QtCore/QString>
30#include <QtGui/QBrush>
31#include <QtGui/QColor>
32#include <QtGui/QTextCharFormat>
33#include <QtGui/QTextCursor>
34#include <QtGui/QTextDocument>
35#include <QtGui/QTextDocumentFragment>
36#include <QtGui/QTextFrame>
37#include <QtGui/QTextList>
38#include <QtGui/QTextTable>
39
40using namespace Cutelee;
41
43 : d_ptr(new MarkupDirectorPrivate(this)), m_builder(builder)
44{
45}
46
48
51{
52 while (!start.atEnd() && start != end) {
53 auto frame = start.currentFrame();
54 if (frame) {
55 auto table = qobject_cast<QTextTable *>(frame);
56 if (table) {
57 start = processTable(start, table);
58 } else {
59 start = processFrame(start, frame);
60 }
61 } else {
62 auto block = start.currentBlock();
63 Q_ASSERT(block.isValid());
64 start = processBlock(start, block);
65 }
66 }
67}
68
70 QTextFrame *frame)
71{
72 if (frame) {
73 processDocumentContents(frame->begin(), frame->end());
74 }
75 if (!it.atEnd())
76 return ++it;
77 return it;
78}
79
81 const QTextBlock &block)
82{
83 if (block.isValid()) {
84 auto fmt = block.blockFormat();
85 auto object = block.document()->objectForFormat(fmt);
86 if (object) {
87 return processObject(it, block, object);
88 } else {
89 return processBlockContents(it, block);
90 }
91 }
92
93 if (!it.atEnd())
94 return ++it;
95 return it;
96}
97
99 QTextTable *table)
100{
101 auto format = table->format();
102
103 auto colLengths = format.columnWidthConstraints();
104
105 auto tableWidth = format.width();
106 QString sWidth;
107
108 if (tableWidth.type() == QTextLength::PercentageLength) {
109 sWidth = QStringLiteral("%1%");
110 sWidth = sWidth.arg(tableWidth.rawValue());
111 } else if (tableWidth.type() == QTextLength::FixedLength) {
112 sWidth = QStringLiteral("%1");
113 sWidth = sWidth.arg(tableWidth.rawValue());
114 }
115
116 m_builder->beginTable(format.cellPadding(), format.cellSpacing(), sWidth);
117
118 auto headerRowCount = format.headerRowCount();
119
120 QVector<QTextTableCell> alreadyProcessedCells;
121
122 for (auto row = 0; row < table->rows(); ++row) {
123 // Put a thead element around here somewhere?
124 // if (row < headerRowCount)
125 // {
126 // beginTableHeader();
127 // }
128
129 m_builder->beginTableRow();
130
131 // Header attribute should really be on cells, not determined by number
132 // of
133 // rows.
134 // http://www.webdesignfromscratch.com/html-tables.cfm
135
136 for (auto column = 0; column < table->columns(); ++column) {
137
138 auto tableCell = table->cellAt(row, column);
139
140 auto columnSpan = tableCell.columnSpan();
141 auto rowSpan = tableCell.rowSpan();
142 if ((rowSpan > 1) || (columnSpan > 1)) {
143 if (alreadyProcessedCells.contains(tableCell)) {
144 // Already processed this cell. Move on.
145 continue;
146 } else {
147 alreadyProcessedCells.append(tableCell);
148 }
149 }
150
151 auto cellWidth = colLengths.at(column);
152
153 QString sCellWidth;
154
155 if (cellWidth.type() == QTextLength::PercentageLength) {
156 sCellWidth = QStringLiteral("%1%");
157 sCellWidth = sCellWidth.arg(cellWidth.rawValue());
158 } else if (cellWidth.type() == QTextLength::FixedLength) {
159 sCellWidth = QStringLiteral("%1");
160 sCellWidth = sCellWidth.arg(cellWidth.rawValue());
161 }
162
163 // TODO: Use THEAD instead
164 if (row < headerRowCount) {
165 m_builder->beginTableHeaderCell(sCellWidth, columnSpan, rowSpan);
166 } else {
167 m_builder->beginTableCell(sCellWidth, columnSpan, rowSpan);
168 }
169
170 processTableCell(tableCell, table);
171
172 if (row < headerRowCount) {
173 m_builder->endTableHeaderCell();
174 } else {
175 m_builder->endTableCell();
176 }
177 }
178 m_builder->endTableRow();
179 }
180 m_builder->endTable();
181
182 if (!it.atEnd())
183 return ++it;
184 return it;
185}
186
188 QTextTable *table)
189{
190 Q_UNUSED(table)
191 processDocumentContents(tableCell.begin(), tableCell.end());
192}
193
194std::pair<QTextFrame::iterator, QTextBlock>
196 QTextList *list)
197{
198 auto style = list->format().style();
199 m_builder->beginList(style);
200 auto block = _block;
201 while (block.isValid() && block.textList()) {
202 m_builder->beginListItem();
203 processBlockContents(it, block);
204 m_builder->endListItem();
205
206 if (!it.atEnd())
207 ++it;
208 block = block.next();
209 if (block.isValid()) {
210 auto obj = block.document()->objectForFormat(block.blockFormat());
211 auto group = qobject_cast<QTextBlockGroup *>(obj);
212 if (group && group != list) {
213 auto pair = processBlockGroup(it, block, group);
214 it = pair.first;
215 block = pair.second;
216 }
217 }
218 }
219 m_builder->endList();
220 return {it, block};
221}
222
225 const QTextBlock &block)
226{
227 auto blockFormat = block.blockFormat();
228 auto blockAlignment = blockFormat.alignment();
229
230 // TODO: decide when to use <h1> etc.
231
232 if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
233 m_builder->insertHorizontalRule();
234 if (!frameIt.atEnd())
235 return ++frameIt;
236 return frameIt;
237 }
238
239 auto it = block.begin();
240
241 // The beginning is the end. This is an empty block. Insert a newline and
242 // move
243 // on.
244 if (it.atEnd()) {
245 m_builder->addNewline();
246 if (!frameIt.atEnd())
247 return ++frameIt;
248 return frameIt;
249 }
250
251 // Don't have p tags inside li tags.
252 if (!block.textList()) {
253 // Don't instruct builders to use margins. The rich text widget doesn't
254 // have
255 // an action for them yet,
256 // So users can't edit them. See bug
257 // http://bugs.kde.org/show_bug.cgi?id=160600
258 m_builder->beginParagraph(
259 blockAlignment //,
260 // blockFormat.topMargin(),
261 // blockFormat.bottomMargin(),
262 // blockFormat.leftMargin(),
263 // blockFormat.rightMargin()
264 );
265 }
266
267 while (!it.atEnd()) {
268 it = processFragment(it, it.fragment(), block.document());
269 }
270
271 // Don't have p tags inside li tags.
272 if (!block.textList()) {
273 m_builder->endParagraph();
274 }
275
276 if (!frameIt.atEnd())
277 return ++frameIt;
278 return frameIt;
279}
280
283 const QTextFragment &fragment,
284 QTextDocument const *doc)
285{
286 // Q_D( MarkupDirector );
287 auto charFormat = fragment.charFormat();
288
289 if (charFormat.objectType() >= QTextFormat::UserObject) {
290 processCustomFragment(fragment, doc);
291 if (!it.atEnd())
292 return ++it;
293 return it;
294 }
295
296 auto textObject = doc->objectForFormat(charFormat);
297 if (textObject)
298 return processCharTextObject(it, fragment, textObject);
299
300 if (fragment.text().at(0).category() == QChar::Separator_Line) {
301 m_builder->addNewline();
302
303 if (!it.atEnd())
304 return ++it;
305 return it;
306 }
307
308 // The order of closing and opening tags can determine whether generated
309 // html
310 // is valid or not.
311 // When processing a document with formatting which appears as
312 // '<b><i>Some</i>
313 // formatted<b> text',
314 // the correct generated output will contain '<strong><em>Some</em>
315 // formatted<strong> text'.
316 // However, processing text which appears as '<i><b>Some</b> formatted<i>
317 // text' might be incorrectly rendered
318 // as '<strong><em>Some</strong> formatted</em> text' if tags which start at
319 // the same fragment are
320 // opened out of order. Here, tags are not nested properly, and the html
321 // would
322 // not be valid or render correctly by unforgiving parsers (like QTextEdit).
323 // One solution is to make the order of opening tags dynamic. In the above
324 // case, the em tag would
325 // be opened before the strong tag '<em><strong>Some</strong> formatted</em>
326 // text'. That would
327 // require knowledge of which tag is going to close first. That might be
328 // possible by examining
329 // the 'next' QTextFragment while processing one.
330 //
331 // The other option is to do pessimistic closing of tags.
332 // In the above case, this means that if a fragment has two or more formats
333 // applied (bold and italic here),
334 // and one of them is closed, then all tags should be closed first. They
335 // will
336 // of course be reopened
337 // if necessary while processing the next fragment.
338 // The above case would be rendered as '<strong><em>Some</em></strong><em>
339 // formatted</em> text'.
340 //
341 // The first option is taken here, as the redundant opening and closing tags
342 // in the second option
343 // didn't appeal.
344 // See testDoubleStartDifferentFinish,
345 // testDoubleStartDifferentFinishReverseOrder
346
348
349 // If a sequence such as '<br /><br />' is imported into a document with
350 // setHtml, LineSeparator characters are inserted. Here I make sure to
351 // put them back.
352 auto sl = fragment.text().split(QChar(QChar::LineSeparator));
353 auto i = sl.begin();
354 auto end = sl.end();
355 auto paraClosed = false;
356 while (i != end) {
357 m_builder->appendLiteralText(*i);
358 ++i;
359 if (i != end) {
360 if (i->isEmpty()) {
361 if (!paraClosed) {
362 m_builder->endParagraph();
363 paraClosed = true;
364 }
365 m_builder->addNewline();
366 } else if (paraClosed) {
367 m_builder->beginParagraph(/* blockAlignment */);
368 paraClosed = false;
369 }
370 }
371 }
372 if (!it.atEnd())
373 ++it;
374
376
377 return it;
378}
379
381 const QTextDocument *doc)
382{
383 Q_UNUSED(fragment)
384 Q_UNUSED(doc)
385}
386
388 const QTextBlock &block,
389 QTextObject *object)
390{
391 auto group = qobject_cast<QTextBlockGroup *>(object);
392 if (group) {
393 return processBlockGroup(it, block, group).first;
394 }
395 if (!it.atEnd())
396 return ++it;
397 return it;
398}
399
400std::pair<QTextFrame::iterator, QTextBlock>
402 const QTextBlock &_block,
403 QTextBlockGroup *blockGroup)
404{
405 auto block = _block;
406 auto lastBlock = _block;
407 auto lastIt = it;
408 auto obj = block.document()->objectForFormat(block.blockFormat());
409 QTextBlockGroup *nextGroup;
410
411 if (!obj)
412 return {lastIt, lastBlock};
413
414 auto group = qobject_cast<QTextBlockGroup *>(obj);
415 if (!group)
416 return {lastIt, lastBlock};
417
418 while (block.isValid()) {
419 if (!group)
420 break;
421
422 block = block.next();
423 if (!it.atEnd())
424 ++it;
425
426 obj = block.document()->objectForFormat(block.blockFormat());
427 if (obj)
428 continue;
429
430 nextGroup = qobject_cast<QTextBlockGroup *>(obj);
431
432 if (group == blockGroup || !nextGroup) {
433 lastBlock = block;
434 lastIt = it;
435 }
436 group = nextGroup;
437 }
438 return {lastIt, lastBlock};
439}
440
441std::pair<QTextFrame::iterator, QTextBlock>
443 const QTextBlock &block,
444 QTextBlockGroup *blockGroup)
445{
446 auto list = qobject_cast<QTextList *>(blockGroup);
447 if (list) {
448 return processList(it, block, list);
449 }
450 return skipBlockGroup(it, block, blockGroup);
451}
452
457
460 const QTextFragment &fragment,
461 QTextObject *textObject)
462{
463 auto fragmentFormat = fragment.charFormat();
464 if (fragmentFormat.isImageFormat()) {
465 auto imageFormat = fragmentFormat.toImageFormat();
466 return processImage(it, imageFormat, textObject->document());
467 }
468 if (!it.atEnd())
469 return ++it;
470 return it;
471}
472
475 const QTextImageFormat &imageFormat,
476 QTextDocument *doc)
477{
478 Q_UNUSED(doc)
479 // TODO: Close any open format elements?
480 m_builder->insertImage(imageFormat.name(), imageFormat.width(),
481 imageFormat.height());
482 if (!it.atEnd())
483 return ++it;
484 return it;
485}
486
488{
489 Q_D(MarkupDirector);
490 // The order of closing elements is determined by the order they were opened
491 // in.
492 // The order of opened elements is in the openElements member list.
493 // see testDifferentStartDoubleFinish and
494 // testDifferentStartDoubleFinishReverseOrder
495
496 if (d->m_openElements.isEmpty())
497 return;
498
499 auto elementsToClose = getElementsToClose(it);
500
501 int previousSize;
502 auto remainingSize = elementsToClose.size();
503 while (!elementsToClose.isEmpty()) {
504 auto tag = d->m_openElements.last();
505 if (elementsToClose.contains(tag)) {
506 switch (tag) {
507 case Strong:
508 m_builder->endStrong();
509 break;
510 case Emph:
511 m_builder->endEmph();
512 break;
513 case Underline:
514 m_builder->endUnderline();
515 break;
516 case StrikeOut:
517 m_builder->endStrikeout();
518 break;
520 m_builder->endFontPointSize();
521 break;
522 case SpanFontFamily:
523 m_builder->endFontFamily();
524 break;
525 case SpanBackground:
526 m_builder->endBackground();
527 break;
528 case SpanForeground:
529 m_builder->endForeground();
530 break;
531 case Anchor:
532 m_builder->endAnchor();
533 break;
534 case SubScript:
535 m_builder->endSubscript();
536 break;
537 case SuperScript:
538 m_builder->endSuperscript();
539 break;
540
541 default:
542 break;
543 }
544 d->m_openElements.removeLast();
545 elementsToClose.remove(tag);
546 }
547 previousSize = remainingSize;
548 remainingSize = elementsToClose.size();
549
550 if (previousSize == remainingSize) {
551 // Iterated once through without closing any tags.
552 // This means that there's overlap in the tags, such as
553 // 'text with <b>some <i>formatting</i></b><i> tags</i>'
554 // See testOverlap.
555 // The top element in openElements must be a blocker, so close it on
556 // next
557 // iteration.
558 elementsToClose.insert(d->m_openElements.last());
559 }
560 }
561}
562
564{
565 Q_D(MarkupDirector);
566 auto fragment = it.fragment();
567
568 if (!fragment.isValid())
569 return;
570
571 auto fragmentFormat = fragment.charFormat();
572 auto elementsToOpenList = getElementsToOpen(it);
573
574 Q_FOREACH (int tag, elementsToOpenList) {
575 switch (tag) {
576 case Strong:
577 m_builder->beginStrong();
578 break;
579 case Emph:
580 m_builder->beginEmph();
581 break;
582 case Underline:
583 m_builder->beginUnderline();
584 break;
585 case StrikeOut:
586 m_builder->beginStrikeout();
587 break;
589 m_builder->beginFontPointSize(fragmentFormat.font().pointSize());
590 d->m_openFontPointSize = fragmentFormat.font().pointSize();
591 break;
592 case SpanFontFamily:
593#if QT_VERSION < QT_VERSION_CHECK(6, 1, 0)
594 d->m_openFontFamily = fragmentFormat.fontFamily();
595#else
596 d->m_openFontFamily = fragmentFormat.fontFamilies().toStringList().first();
597#endif
598 m_builder->beginFontFamily(d->m_openFontFamily);
599 break;
600 case SpanBackground:
601 m_builder->beginBackground(fragmentFormat.background());
602 d->m_openBackground = fragmentFormat.background();
603 break;
604 case SpanForeground:
605 m_builder->beginForeground(fragmentFormat.foreground());
606 d->m_openForeground = fragmentFormat.foreground();
607 break;
608 case Anchor: {
609 // TODO: Multiple anchor names here.
610 auto anchorNames = fragmentFormat.anchorNames();
611 if (!anchorNames.isEmpty()) {
612 while (!anchorNames.isEmpty()) {
613 auto n = anchorNames.last();
614 anchorNames.removeLast();
615 if (anchorNames.isEmpty()) {
616 // Doesn't matter if anchorHref is empty.
617 m_builder->beginAnchor(fragmentFormat.anchorHref(), n);
618 break;
619 } else {
620 // Empty <a> tags allow multiple names for the same
621 // section.
622 m_builder->beginAnchor(QString(), n);
623 m_builder->endAnchor();
624 }
625 }
626 } else {
627 m_builder->beginAnchor(fragmentFormat.anchorHref());
628 }
629 d->m_openAnchorHref = fragmentFormat.anchorHref();
630 break;
631 }
632 case SuperScript:
633 m_builder->beginSuperscript();
634 break;
635 case SubScript:
636 m_builder->beginSubscript();
637 break;
638 default:
639 break;
640 }
641 d->m_openElements.append(tag);
642 d->m_elementsToOpen.remove(tag);
643 }
644}
645
647{
648 Q_D(const MarkupDirector);
649 QSet<int> closedElements;
650
651 if (it.atEnd()) {
652 // End of block?. Close all open tags.
653#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
654 auto elementsToClose = d->m_openElements.toSet();
655#else
656 auto elementsToClose = QSet<int>(d->m_openElements.begin(), d->m_openElements.end());
657#endif
658 return elementsToClose.unite(d->m_elementsToOpen);
659 }
660
661 auto fragment = it.fragment();
662
663 if (!fragment.isValid())
664 return closedElements;
665
666 auto fragmentFormat = fragment.charFormat();
667
668 auto fontWeight = fragmentFormat.fontWeight();
669 auto fontItalic = fragmentFormat.fontItalic();
670 auto fontUnderline = fragmentFormat.fontUnderline();
671 auto fontStrikeout = fragmentFormat.fontStrikeOut();
672
673 auto fontForeground = fragmentFormat.foreground();
674 auto fontBackground = fragmentFormat.background();
675
676#if QT_VERSION < QT_VERSION_CHECK(6, 1, 0)
677 auto fontFamily = fragmentFormat.fontFamily();
678#else
679 const QStringList fontFamilies = fragmentFormat.fontFamilies().toStringList();
680 auto fontFamily = !fontFamilies.empty() ? fontFamilies.first() : QString();
681#endif
682 auto fontPointSize = fragmentFormat.font().pointSize();
683 auto anchorHref = fragmentFormat.anchorHref();
684
685 auto vAlign = fragmentFormat.verticalAlignment();
686 auto superscript = (vAlign == QTextCharFormat::AlignSuperScript);
687 auto subscript = (vAlign == QTextCharFormat::AlignSubScript);
688
689 if (!fontStrikeout
690 && (d->m_openElements.contains(StrikeOut)
691 || d->m_elementsToOpen.contains(StrikeOut))) {
692 closedElements.insert(StrikeOut);
693 }
694
695 if (!fontUnderline
696 && (d->m_openElements.contains(Underline)
697 || d->m_elementsToOpen.contains(Underline))
698 && !(d->m_openElements.contains(Anchor)
699 || d->m_elementsToOpen.contains(Anchor))) {
700 closedElements.insert(Underline);
701 }
702
703 if (!fontItalic
704 && (d->m_openElements.contains(Emph)
705 || d->m_elementsToOpen.contains(Emph))) {
706 closedElements.insert(Emph);
707 }
708
709 if (fontWeight != QFont::Bold
710 && (d->m_openElements.contains(Strong)
711 || d->m_elementsToOpen.contains(Strong))) {
712 closedElements.insert(Strong);
713 }
714
715 if ((d->m_openElements.contains(SpanFontPointSize)
716 || d->m_elementsToOpen.contains(SpanFontPointSize))
717 && (d->m_openFontPointSize != fontPointSize)) {
718 closedElements.insert(SpanFontPointSize);
719 }
720
721 if ((d->m_openElements.contains(SpanFontFamily)
722 || d->m_elementsToOpen.contains(SpanFontFamily))
723 && (d->m_openFontFamily != fontFamily)) {
724 closedElements.insert(SpanFontFamily);
725 }
726
727 if ((d->m_openElements.contains(SpanBackground)
728 && (d->m_openBackground != fontBackground))
729 || (d->m_elementsToOpen.contains(SpanBackground)
730 && (d->m_backgroundToOpen != fontBackground))) {
731 closedElements.insert(SpanBackground);
732 }
733
734 if ((d->m_openElements.contains(SpanForeground)
735 && (d->m_openForeground != fontForeground))
736 || (d->m_elementsToOpen.contains(SpanForeground)
737 && (d->m_foregroundToOpen != fontForeground))) {
738 closedElements.insert(SpanForeground);
739 }
740
741 if ((d->m_openElements.contains(Anchor)
742 && (d->m_openAnchorHref != anchorHref))
743 || (d->m_elementsToOpen.contains(Anchor)
744 && (d->m_anchorHrefToOpen != anchorHref))) {
745 closedElements.insert(Anchor);
746 }
747
748 if (!subscript
749 && (d->m_openElements.contains(SubScript)
750 || d->m_elementsToOpen.contains(SubScript))) {
751 closedElements.insert(SubScript);
752 }
753
754 if (!superscript
755 && (d->m_openElements.contains(SuperScript)
756 || d->m_elementsToOpen.contains(SuperScript))) {
757 closedElements.insert(SuperScript);
758 }
759 return closedElements;
760}
761
763{
764 Q_D(MarkupDirector);
765 auto fragment = it.fragment();
766 if (!fragment.isValid()) {
767 return QList<int>();
768 }
769 auto fragmentFormat = fragment.charFormat();
770
771 auto fontWeight = fragmentFormat.fontWeight();
772 auto fontItalic = fragmentFormat.fontItalic();
773 auto fontUnderline = fragmentFormat.fontUnderline();
774 auto fontStrikeout = fragmentFormat.fontStrikeOut();
775
776 auto fontForeground = fragmentFormat.foreground();
777 auto fontBackground = fragmentFormat.background();
778
779#if QT_VERSION < QT_VERSION_CHECK(6, 1, 0)
780 auto fontFamily = fragmentFormat.fontFamily();
781#else
782 const QStringList fontFamilies = fragmentFormat.fontFamilies().toStringList();
783 auto fontFamily = !fontFamilies.empty() ? fontFamilies.first() : QString();
784#endif
785 auto fontPointSize = fragmentFormat.font().pointSize();
786 auto anchorHref = fragmentFormat.anchorHref();
787
788 auto vAlign = fragmentFormat.verticalAlignment();
789 auto superscript = (vAlign == QTextCharFormat::AlignSuperScript);
790 auto subscript = (vAlign == QTextCharFormat::AlignSubScript);
791
792 if (superscript && !(d->m_openElements.contains(SuperScript))) {
793 d->m_elementsToOpen.insert(SuperScript);
794 }
795
796 if (subscript && !(d->m_openElements.contains(SubScript))) {
797 d->m_elementsToOpen.insert(SubScript);
798 }
799
800 if (!anchorHref.isEmpty() && !(d->m_openElements.contains(Anchor))
801 && (d->m_openAnchorHref != anchorHref)) {
802 d->m_elementsToOpen.insert(Anchor);
803 d->m_anchorHrefToOpen = anchorHref;
804 }
805
806 if (fontForeground != Qt::NoBrush
807 && !(d->m_openElements.contains(SpanForeground)) // Can only open one
808 // foreground element
809 // at a time.
810 && (fontForeground != d->m_openForeground)
811 && !((d->m_openElements.contains(
812 Anchor) // Links can't have a foreground color.
813 || d->m_elementsToOpen.contains(Anchor)))) {
814 d->m_elementsToOpen.insert(SpanForeground);
815 d->m_foregroundToOpen = fontForeground;
816 }
817
818 if (fontBackground != Qt::NoBrush
819 && !(d->m_openElements.contains(SpanBackground))
820 && (fontBackground != d->m_openBackground)) {
821 d->m_elementsToOpen.insert(SpanBackground);
822 d->m_backgroundToOpen = fontBackground;
823 }
824
825 if (!fontFamily.isEmpty() && !(d->m_openElements.contains(SpanFontFamily))
826 && (fontFamily != d->m_openFontFamily)) {
827 d->m_elementsToOpen.insert(SpanFontFamily);
828 d->m_fontFamilyToOpen = fontFamily;
829 }
830
831 if ((QTextCharFormat().font().pointSize()
832 != fontPointSize) // Different from the default.
833 && !(d->m_openElements.contains(SpanFontPointSize))
834 && (fontPointSize != d->m_openFontPointSize)) {
835 d->m_elementsToOpen.insert(SpanFontPointSize);
836 d->m_fontPointSizeToOpen = fontPointSize;
837 }
838
839 // Only open a new bold tag if one is not already open.
840 // eg, <b>some <i>mixed</i> format</b> should be as is, rather than
841 // <b>some </b><b><i>mixed</i></b><b> format</b>
842
843 if (fontWeight == QFont::Bold && !(d->m_openElements.contains(Strong))) {
844 d->m_elementsToOpen.insert(Strong);
845 }
846
847 if (fontItalic && !(d->m_openElements.contains(Emph))) {
848 d->m_elementsToOpen.insert(Emph);
849 }
850
851 if (fontUnderline && !(d->m_openElements.contains(Underline))
852 && !(d->m_openElements.contains(Anchor)
853 || d->m_elementsToOpen.contains(
854 Anchor)) // Can't change the underline state of a link.
855 ) {
856 d->m_elementsToOpen.insert(Underline);
857 }
858
859 if (fontStrikeout && !(d->m_openElements.contains(StrikeOut))) {
860 d->m_elementsToOpen.insert(StrikeOut);
861 }
862
863 if (d->m_elementsToOpen.size() <= 1) {
864#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
865 auto elementsToClose = d->m_elementsToOpen.toList();
866#else
867 return QList<int>(d->m_elementsToOpen.begin(), d->m_elementsToOpen.end());
868#endif
869 }
870 return sortOpeningOrder(d->m_elementsToOpen, it);
871}
872
874 QTextBlock::iterator it) const
875{
876 QList<int> sortedOpenedElements;
877
878 // This is an insertion sort in a way. elements in openingOrder are assumed
879 // to
880 // be out of order.
881 // The rest of the block is traversed until there are no more elements to
882 // sort, or the end is reached.
883 while (openingOrder.size() != 0) {
884 if (!it.atEnd()) {
885 it++;
886
887 if (!it.atEnd()) {
888 // Because I've iterated, this returns the elements that will
889 // be closed by the next fragment.
890 auto elementsToClose = getElementsToClose(it);
891
892 // The exact order these are opened in is irrelevant, as all
893 // will be
894 // closed on the same block.
895 // See testDoubleFormat.
896 Q_FOREACH (int tag, elementsToClose) {
897 if (openingOrder.remove(tag)) {
898 sortedOpenedElements.prepend(tag);
899 }
900 }
901 }
902 } else {
903 // End of block. Need to close all open elements.
904 // Order irrelevant in this case.
905 Q_FOREACH (int tag, openingOrder) {
906 sortedOpenedElements.prepend(tag);
907 }
908 break;
909 }
910 }
911 return sortedOpenedElements;
912}
Interface for creating marked-up text output.
virtual QTextFrame::iterator processBlock(QTextFrame::iterator it, const QTextBlock &block)
virtual void processTableCell(const QTextTableCell &tableCell, QTextTable *table)
virtual void processOpeningElements(QTextBlock::iterator it)
virtual QTextBlock::iterator processCharTextObject(QTextBlock::iterator it, const QTextFragment &fragment, QTextObject *textObject)
virtual QTextFrame::iterator processTable(QTextFrame::iterator it, QTextTable *table)
virtual void processCustomFragment(const QTextFragment &fragment, QTextDocument const *doc)
virtual void processDocument(QTextDocument *doc)
virtual QList< int > getElementsToOpen(QTextBlock::iterator it)
AbstractMarkupBuilder * m_builder
virtual QTextBlock::iterator processImage(QTextBlock::iterator it, const QTextImageFormat &imageFormat, QTextDocument *doc)
@ SpanFontPointSize
A font family altering span tag is open.
@ SpanForeground
An anchor tag is open.
@ SuperScript
No tags are open.
@ Underline
A emphasis tag is open.
@ SubScript
A superscript tag is open.
@ Emph
A strong tag is open.
@ Anchor
A subscript tag is open.
@ SpanFontFamily
A background altering span tag is open.
@ Strong
A font size altering span tag is open.
@ StrikeOut
An underline tag is open.
@ SpanBackground
A foreground altering span tag is open.
MarkupDirector(AbstractMarkupBuilder *builder)
virtual QTextFrame::iterator processFrame(QTextFrame::iterator it, QTextFrame *frame)
virtual std::pair< QTextFrame::iterator, QTextBlock > processBlockGroup(QTextFrame::iterator it, const QTextBlock &block, QTextBlockGroup *textBlockGroup)
virtual std::pair< QTextFrame::iterator, QTextBlock > processList(QTextFrame::iterator it, const QTextBlock &block, QTextList *textList)
virtual QSet< int > getElementsToClose(QTextBlock::iterator it) const
QList< int > sortOpeningOrder(QSet< int > openingTags, QTextBlock::iterator it) const
void processDocumentContents(QTextFrame::iterator begin, QTextFrame::iterator end)
virtual void processClosingElements(QTextBlock::iterator it)
virtual QTextFrame::iterator processObject(QTextFrame::iterator it, const QTextBlock &block, QTextObject *textObject)
virtual QTextFrame::iterator processBlockContents(QTextFrame::iterator it, const QTextBlock &block)
std::pair< QTextFrame::iterator, QTextBlock > skipBlockGroup(QTextFrame::iterator it, const QTextBlock &_block, QTextBlockGroup *blockGroup)
virtual QTextBlock::iterator processFragment(QTextBlock::iterator it, const QTextFragment &fragment, QTextDocument const *doc)
The Cutelee namespace holds all public Cutelee API.
Definition Mainpage.dox:8
Separator_Line
Category category() const const
iterator begin()
bool empty() const const
T & first()
void prepend(const T &value)
bool remove(const T &value)
int size() const const
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
const QChar at(int position) const const
bool atEnd() const const
QTextFragment fragment() const const
iterator begin() const const
QTextBlockFormat blockFormat() const const
const QTextDocument * document() const const
bool isValid() const const
QTextList * textList() const const
Qt::Alignment alignment() const const
int fontWeight() const const
QTextObject * objectForFormat(const QTextFormat &f) const const
QTextFrame * rootFrame() const const
BlockTrailingHorizontalRulerWidth
QTextCharFormat charFormat() const const
QString text() const const
bool atEnd() const const
QTextBlock currentBlock() const const
QTextFrame * currentFrame() const const
iterator begin() const const
iterator end() const const
qreal height() const const
QString name() const const
qreal width() const const
QTextListFormat format() const const
Style style() const const
QTextDocument * document() const const
QTextTableCell cellAt(int row, int column) const const
int columns() const const
QTextTableFormat format() const const
int rows() const const
QTextFrame::iterator begin() const const
int columnSpan() const const
QTextFrame::iterator end() const const
QVector< QTextLength > columnWidthConstraints() const const
void append(const T &value)
bool contains(const T &value) const const