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 
40 using namespace Cutelee;
41 
43  : d_ptr(new MarkupDirectorPrivate(this)), m_builder(builder)
44 {
45 }
46 
48 
49 void 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 
69 QTextFrame::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 
80 QTextFrame::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 
98 QTextFrame::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  }
180  m_builder->endTable();
181 
182  if (!it.atEnd())
183  return ++it;
184  return it;
185 }
186 
187 void MarkupDirector::processTableCell(const QTextTableCell &tableCell,
188  QTextTable *table)
189 {
190  Q_UNUSED(table)
191  processDocumentContents(tableCell.begin(), tableCell.end());
192 }
193 
194 std::pair<QTextFrame::iterator, QTextBlock>
195 MarkupDirector::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  }
219  m_builder->endList();
220  return {it, block};
221 }
222 
223 QTextFrame::iterator
224 MarkupDirector::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 
281 QTextBlock::iterator
282 MarkupDirector::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 
380 void MarkupDirector::processCustomFragment(const QTextFragment &fragment,
381  const QTextDocument *doc)
382 {
383  Q_UNUSED(fragment)
384  Q_UNUSED(doc)
385 }
386 
387 QTextFrame::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 
400 std::pair<QTextFrame::iterator, QTextBlock>
401 MarkupDirector::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 
441 std::pair<QTextFrame::iterator, QTextBlock>
442 MarkupDirector::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 
453 void MarkupDirector::processDocument(QTextDocument *doc)
454 {
455  processFrame(QTextFrame::iterator(), doc->rootFrame());
456 }
457 
458 QTextBlock::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 
473 QTextBlock::iterator
474 MarkupDirector::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 
487 void 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:
508  m_builder->endStrong();
509  break;
510  case Emph:
511  m_builder->endEmph();
512  break;
513  case Underline:
515  break;
516  case StrikeOut:
518  break;
519  case SpanFontPointSize:
521  break;
522  case SpanFontFamily:
524  break;
525  case SpanBackground:
527  break;
528  case SpanForeground:
530  break;
531  case Anchor:
532  m_builder->endAnchor();
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 
563 void 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:
580  m_builder->beginEmph();
581  break;
582  case Underline:
584  break;
585  case StrikeOut:
587  break;
588  case SpanFontPointSize:
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.
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:
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 
646 QSet<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 
762 QList<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 
873 QList<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