Cutelee 6.2.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 d->m_openFontFamily = fragmentFormat.fontFamilies().toStringList().first();
594 m_builder->beginFontFamily(d->m_openFontFamily);
595 break;
596 case SpanBackground:
597 m_builder->beginBackground(fragmentFormat.background());
598 d->m_openBackground = fragmentFormat.background();
599 break;
600 case SpanForeground:
601 m_builder->beginForeground(fragmentFormat.foreground());
602 d->m_openForeground = fragmentFormat.foreground();
603 break;
604 case Anchor: {
605 // TODO: Multiple anchor names here.
606 auto anchorNames = fragmentFormat.anchorNames();
607 if (!anchorNames.isEmpty()) {
608 while (!anchorNames.isEmpty()) {
609 auto n = anchorNames.last();
610 anchorNames.removeLast();
611 if (anchorNames.isEmpty()) {
612 // Doesn't matter if anchorHref is empty.
613 m_builder->beginAnchor(fragmentFormat.anchorHref(), n);
614 break;
615 } else {
616 // Empty <a> tags allow multiple names for the same
617 // section.
618 m_builder->beginAnchor(QString(), n);
619 m_builder->endAnchor();
620 }
621 }
622 } else {
623 m_builder->beginAnchor(fragmentFormat.anchorHref());
624 }
625 d->m_openAnchorHref = fragmentFormat.anchorHref();
626 break;
627 }
628 case SuperScript:
629 m_builder->beginSuperscript();
630 break;
631 case SubScript:
632 m_builder->beginSubscript();
633 break;
634 default:
635 break;
636 }
637 d->m_openElements.append(tag);
638 d->m_elementsToOpen.remove(tag);
639 }
640}
641
643{
644 Q_D(const MarkupDirector);
645 QSet<int> closedElements;
646
647 if (it.atEnd()) {
648 // End of block?. Close all open tags.
649 auto elementsToClose = QSet<int>(d->m_openElements.begin(), d->m_openElements.end());
650 return elementsToClose.unite(d->m_elementsToOpen);
651 }
652
653 auto fragment = it.fragment();
654
655 if (!fragment.isValid())
656 return closedElements;
657
658 auto fragmentFormat = fragment.charFormat();
659
660 auto fontWeight = fragmentFormat.fontWeight();
661 auto fontItalic = fragmentFormat.fontItalic();
662 auto fontUnderline = fragmentFormat.fontUnderline();
663 auto fontStrikeout = fragmentFormat.fontStrikeOut();
664
665 auto fontForeground = fragmentFormat.foreground();
666 auto fontBackground = fragmentFormat.background();
667
668 const QStringList fontFamilies = fragmentFormat.fontFamilies().toStringList();
669 auto fontFamily = !fontFamilies.empty() ? fontFamilies.first() : QString();
670 auto fontPointSize = fragmentFormat.font().pointSize();
671 auto anchorHref = fragmentFormat.anchorHref();
672
673 auto vAlign = fragmentFormat.verticalAlignment();
674 auto superscript = (vAlign == QTextCharFormat::AlignSuperScript);
675 auto subscript = (vAlign == QTextCharFormat::AlignSubScript);
676
677 if (!fontStrikeout
678 && (d->m_openElements.contains(StrikeOut)
679 || d->m_elementsToOpen.contains(StrikeOut))) {
680 closedElements.insert(StrikeOut);
681 }
682
683 if (!fontUnderline
684 && (d->m_openElements.contains(Underline)
685 || d->m_elementsToOpen.contains(Underline))
686 && !(d->m_openElements.contains(Anchor)
687 || d->m_elementsToOpen.contains(Anchor))) {
688 closedElements.insert(Underline);
689 }
690
691 if (!fontItalic
692 && (d->m_openElements.contains(Emph)
693 || d->m_elementsToOpen.contains(Emph))) {
694 closedElements.insert(Emph);
695 }
696
697 if (fontWeight != QFont::Bold
698 && (d->m_openElements.contains(Strong)
699 || d->m_elementsToOpen.contains(Strong))) {
700 closedElements.insert(Strong);
701 }
702
703 if ((d->m_openElements.contains(SpanFontPointSize)
704 || d->m_elementsToOpen.contains(SpanFontPointSize))
705 && (d->m_openFontPointSize != fontPointSize)) {
706 closedElements.insert(SpanFontPointSize);
707 }
708
709 if ((d->m_openElements.contains(SpanFontFamily)
710 || d->m_elementsToOpen.contains(SpanFontFamily))
711 && (d->m_openFontFamily != fontFamily)) {
712 closedElements.insert(SpanFontFamily);
713 }
714
715 if ((d->m_openElements.contains(SpanBackground)
716 && (d->m_openBackground != fontBackground))
717 || (d->m_elementsToOpen.contains(SpanBackground)
718 && (d->m_backgroundToOpen != fontBackground))) {
719 closedElements.insert(SpanBackground);
720 }
721
722 if ((d->m_openElements.contains(SpanForeground)
723 && (d->m_openForeground != fontForeground))
724 || (d->m_elementsToOpen.contains(SpanForeground)
725 && (d->m_foregroundToOpen != fontForeground))) {
726 closedElements.insert(SpanForeground);
727 }
728
729 if ((d->m_openElements.contains(Anchor)
730 && (d->m_openAnchorHref != anchorHref))
731 || (d->m_elementsToOpen.contains(Anchor)
732 && (d->m_anchorHrefToOpen != anchorHref))) {
733 closedElements.insert(Anchor);
734 }
735
736 if (!subscript
737 && (d->m_openElements.contains(SubScript)
738 || d->m_elementsToOpen.contains(SubScript))) {
739 closedElements.insert(SubScript);
740 }
741
742 if (!superscript
743 && (d->m_openElements.contains(SuperScript)
744 || d->m_elementsToOpen.contains(SuperScript))) {
745 closedElements.insert(SuperScript);
746 }
747 return closedElements;
748}
749
751{
752 Q_D(MarkupDirector);
753 auto fragment = it.fragment();
754 if (!fragment.isValid()) {
755 return QList<int>();
756 }
757 auto fragmentFormat = fragment.charFormat();
758
759 auto fontWeight = fragmentFormat.fontWeight();
760 auto fontItalic = fragmentFormat.fontItalic();
761 auto fontUnderline = fragmentFormat.fontUnderline();
762 auto fontStrikeout = fragmentFormat.fontStrikeOut();
763
764 auto fontForeground = fragmentFormat.foreground();
765 auto fontBackground = fragmentFormat.background();
766
767 const QStringList fontFamilies = fragmentFormat.fontFamilies().toStringList();
768 auto fontFamily = !fontFamilies.empty() ? fontFamilies.first() : QString();
769 auto fontPointSize = fragmentFormat.font().pointSize();
770 auto anchorHref = fragmentFormat.anchorHref();
771
772 auto vAlign = fragmentFormat.verticalAlignment();
773 auto superscript = (vAlign == QTextCharFormat::AlignSuperScript);
774 auto subscript = (vAlign == QTextCharFormat::AlignSubScript);
775
776 if (superscript && !(d->m_openElements.contains(SuperScript))) {
777 d->m_elementsToOpen.insert(SuperScript);
778 }
779
780 if (subscript && !(d->m_openElements.contains(SubScript))) {
781 d->m_elementsToOpen.insert(SubScript);
782 }
783
784 if (!anchorHref.isEmpty() && !(d->m_openElements.contains(Anchor))
785 && (d->m_openAnchorHref != anchorHref)) {
786 d->m_elementsToOpen.insert(Anchor);
787 d->m_anchorHrefToOpen = anchorHref;
788 }
789
790 if (fontForeground != Qt::NoBrush
791 && !(d->m_openElements.contains(SpanForeground)) // Can only open one
792 // foreground element
793 // at a time.
794 && (fontForeground != d->m_openForeground)
795 && !((d->m_openElements.contains(
796 Anchor) // Links can't have a foreground color.
797 || d->m_elementsToOpen.contains(Anchor)))) {
798 d->m_elementsToOpen.insert(SpanForeground);
799 d->m_foregroundToOpen = fontForeground;
800 }
801
802 if (fontBackground != Qt::NoBrush
803 && !(d->m_openElements.contains(SpanBackground))
804 && (fontBackground != d->m_openBackground)) {
805 d->m_elementsToOpen.insert(SpanBackground);
806 d->m_backgroundToOpen = fontBackground;
807 }
808
809 if (!fontFamily.isEmpty() && !(d->m_openElements.contains(SpanFontFamily))
810 && (fontFamily != d->m_openFontFamily)) {
811 d->m_elementsToOpen.insert(SpanFontFamily);
812 d->m_fontFamilyToOpen = fontFamily;
813 }
814
815 if ((QTextCharFormat().font().pointSize()
816 != fontPointSize) // Different from the default.
817 && !(d->m_openElements.contains(SpanFontPointSize))
818 && (fontPointSize != d->m_openFontPointSize)) {
819 d->m_elementsToOpen.insert(SpanFontPointSize);
820 d->m_fontPointSizeToOpen = fontPointSize;
821 }
822
823 // Only open a new bold tag if one is not already open.
824 // eg, <b>some <i>mixed</i> format</b> should be as is, rather than
825 // <b>some </b><b><i>mixed</i></b><b> format</b>
826
827 if (fontWeight == QFont::Bold && !(d->m_openElements.contains(Strong))) {
828 d->m_elementsToOpen.insert(Strong);
829 }
830
831 if (fontItalic && !(d->m_openElements.contains(Emph))) {
832 d->m_elementsToOpen.insert(Emph);
833 }
834
835 if (fontUnderline && !(d->m_openElements.contains(Underline))
836 && !(d->m_openElements.contains(Anchor)
837 || d->m_elementsToOpen.contains(
838 Anchor)) // Can't change the underline state of a link.
839 ) {
840 d->m_elementsToOpen.insert(Underline);
841 }
842
843 if (fontStrikeout && !(d->m_openElements.contains(StrikeOut))) {
844 d->m_elementsToOpen.insert(StrikeOut);
845 }
846
847 if (d->m_elementsToOpen.size() <= 1) {
848 return QList<int>(d->m_elementsToOpen.begin(), d->m_elementsToOpen.end());
849 }
850 return sortOpeningOrder(d->m_elementsToOpen, it);
851}
852
854 QTextBlock::iterator it) const
855{
856 QList<int> sortedOpenedElements;
857
858 // This is an insertion sort in a way. elements in openingOrder are assumed
859 // to
860 // be out of order.
861 // The rest of the block is traversed until there are no more elements to
862 // sort, or the end is reached.
863 while (openingOrder.size() != 0) {
864 if (!it.atEnd()) {
865 it++;
866
867 if (!it.atEnd()) {
868 // Because I've iterated, this returns the elements that will
869 // be closed by the next fragment.
870 auto elementsToClose = getElementsToClose(it);
871
872 // The exact order these are opened in is irrelevant, as all
873 // will be
874 // closed on the same block.
875 // See testDoubleFormat.
876 Q_FOREACH (int tag, elementsToClose) {
877 if (openingOrder.remove(tag)) {
878 sortedOpenedElements.prepend(tag);
879 }
880 }
881 }
882 } else {
883 // End of block. Need to close all open elements.
884 // Order irrelevant in this case.
885 Q_FOREACH (int tag, openingOrder) {
886 sortedOpenedElements.prepend(tag);
887 }
888 break;
889 }
890 }
891 return sortedOpenedElements;
892}
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
QChar::Category category(char32_t ucs4)
void append(QList< T > &&value)
QList< T >::iterator begin()
bool contains(const AT &value) const const
bool empty() const const
QList< T >::iterator end()
T & first()
void prepend(QList< T >::parameter_type value)
QSet< T >::iterator insert(QSet< T >::const_iterator it, const T &value)
bool remove(const T &value)
qsizetype size() const const
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool atEnd() const const
QTextFragment fragment() const const
QTextBlock::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
QTextFrame::iterator begin() const const
QTextFrame::iterator end() const const
qreal height() const const
QString name() const const
qreal width() const const
QTextListFormat format() const const
QTextListFormat::Style style() const const
QTextDocument * document() const const
QTextTableCell cellAt(const QTextCursor &cursor) 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
QList< QTextLength > columnWidthConstraints() const const