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
49void MarkupDirector::processDocumentContents(QTextFrame::iterator start,
50 QTextFrame::iterator end)
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
69QTextFrame::iterator MarkupDirector::processFrame(QTextFrame::iterator it,
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
80QTextFrame::iterator MarkupDirector::processBlock(QTextFrame::iterator it,
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
98QTextFrame::iterator MarkupDirector::processTable(QTextFrame::iterator it,
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
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) {
174 } else {
176 }
177 }
179 }
181
182 if (!it.atEnd())
183 return ++it;
184 return it;
185}
186
187void MarkupDirector::processTableCell(const QTextTableCell &tableCell,
188 QTextTable *table)
189{
190 Q_UNUSED(table)
191 processDocumentContents(tableCell.begin(), tableCell.end());
192}
193
194std::pair<QTextFrame::iterator, QTextBlock>
195MarkupDirector::processList(QTextFrame::iterator it, const QTextBlock &_block,
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()) {
203 processBlockContents(it, block);
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 }
220 return {it, block};
221}
222
223QTextFrame::iterator
224MarkupDirector::processBlockContents(QTextFrame::iterator frameIt,
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)) {
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()) {
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
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()) {
274 }
275
276 if (!frameIt.atEnd())
277 return ++frameIt;
278 return frameIt;
279}
280
281QTextBlock::iterator
282MarkupDirector::processFragment(QTextBlock::iterator it,
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) {
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) {
358 ++i;
359 if (i != end) {
360 if (i->isEmpty()) {
361 if (!paraClosed) {
363 paraClosed = true;
364 }
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
380void MarkupDirector::processCustomFragment(const QTextFragment &fragment,
381 const QTextDocument *doc)
382{
383 Q_UNUSED(fragment)
384 Q_UNUSED(doc)
385}
386
387QTextFrame::iterator MarkupDirector::processObject(QTextFrame::iterator it,
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>
401MarkupDirector::skipBlockGroup(QTextFrame::iterator it,
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>
442MarkupDirector::processBlockGroup(QTextFrame::iterator it,
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
453void MarkupDirector::processDocument(QTextDocument *doc)
454{
455 processFrame(QTextFrame::iterator(), doc->rootFrame());
456}
457
458QTextBlock::iterator
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
473QTextBlock::iterator
474MarkupDirector::processImage(QTextBlock::iterator it,
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
487void MarkupDirector::processClosingElements(QTextBlock::iterator it)
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:
509 break;
510 case Emph:
512 break;
513 case Underline:
515 break;
516 case StrikeOut:
518 break;
521 break;
522 case SpanFontFamily:
524 break;
525 case SpanBackground:
527 break;
528 case SpanForeground:
530 break;
531 case Anchor:
533 break;
534 case SubScript:
536 break;
537 case SuperScript:
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
563void MarkupDirector::processOpeningElements(QTextBlock::iterator it)
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:
578 break;
579 case Emph:
581 break;
582 case Underline:
584 break;
585 case StrikeOut:
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.
624 }
625 }
626 } else {
627 m_builder->beginAnchor(fragmentFormat.anchorHref());
628 }
629 d->m_openAnchorHref = fragmentFormat.anchorHref();
630 break;
631 }
632 case SuperScript:
634 break;
635 case SubScript:
637 break;
638 default:
639 break;
640 }
641 d->m_openElements.append(tag);
642 d->m_elementsToOpen.remove(tag);
643 }
644}
645
646QSet<int> MarkupDirector::getElementsToClose(QTextBlock::iterator it) const
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
762QList<int> MarkupDirector::getElementsToOpen(QTextBlock::iterator it)
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
873QList<int> MarkupDirector::sortOpeningOrder(QSet<int> openingOrder,
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 void beginAnchor(const QString &href={}, const QString &name={})=0
virtual void beginForeground(const QBrush &brush)=0
virtual void beginListItem()=0
virtual void beginParagraph(Qt::Alignment a=Qt::AlignLeft, qreal top=0.0, qreal bottom=0.0, qreal left=0.0, qreal right=0.0)=0
virtual void endBackground()=0
virtual void beginFontFamily(const QString &family)=0
virtual void beginTableRow()=0
virtual void beginList(QTextListFormat::Style style)=0
virtual void endTableHeaderCell()=0
virtual void beginTableHeaderCell(const QString &width, int colSpan, int rowSpan)=0
virtual void endForeground()=0
virtual void beginFontPointSize(int size)=0
virtual void beginStrikeout()=0
virtual void endFontPointSize()=0
virtual void beginTableCell(const QString &width, int colSpan, int rowSpan)=0
virtual void beginSuperscript()=0
virtual void beginTable(qreal cellpadding, qreal cellspacing, const QString &width)=0
virtual void insertImage(const QString &url, qreal width, qreal height)=0
virtual void endSuperscript()=0
virtual void beginBackground(const QBrush &brush)=0
virtual void beginUnderline()=0
virtual void beginSubscript()=0
virtual void appendLiteralText(const QString &text)=0
virtual void insertHorizontalRule(int width=-1)=0
virtual void endFontFamily()=0
Instructs a builder object to create markup 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