File indexing completed on 2024-05-19 05:21:46

0001 /*
0002   SPDX-FileCopyrightText: 2019-2020 Montel Laurent <montel@kde.org>
0003 
0004   SPDX-License-Identifier: LGPL-2.0-or-later
0005 
0006 */
0007 
0008 #include "markupdirector.h"
0009 #include "markupdirector_p.h"
0010 
0011 #include "abstractmarkupbuilder.h"
0012 
0013 #include <QBrush>
0014 #include <QColor>
0015 #include <QFlags>
0016 #include <QMap>
0017 #include <QStack>
0018 #include <QString>
0019 #include <QTextCharFormat>
0020 #include <QTextCursor>
0021 #include <QTextDocument>
0022 #include <QTextDocumentFragment>
0023 #include <QTextFrame>
0024 #include <QTextList>
0025 #include <QTextTable>
0026 
0027 #include <QDebug>
0028 using namespace KPIMTextEdit;
0029 
0030 MarkupDirector::MarkupDirector(KPIMTextEdit::AbstractMarkupBuilder *builder)
0031     : d_ptr(new MarkupDirectorPrivate(this))
0032     , m_builder(builder)
0033 {
0034 }
0035 
0036 MarkupDirector::~MarkupDirector()
0037 {
0038     delete d_ptr;
0039 }
0040 
0041 // #define ADD_HEADER_SUPPORT 1
0042 
0043 QTextFrame::iterator MarkupDirector::processBlockContents(QTextFrame::iterator frameIt, const QTextBlock &block)
0044 {
0045     // Same code as grantlee  but interpret margin
0046 
0047     const auto blockFormat = block.blockFormat();
0048     const auto blockAlignment = blockFormat.alignment();
0049     const bool leftToRightText = block.textDirection() == Qt::RightToLeft;
0050 
0051     // TODO: decide when to use <h1> etc.
0052 #ifdef ADD_HEADER_SUPPORT
0053     if (blockFormat.headingLevel() > 0) {
0054         // Header
0055         qDebug() << " header " << blockFormat.headingLevel();
0056         m_builder->beginHeader(blockFormat.headingLevel());
0057     }
0058 #endif
0059     if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
0060         m_builder->insertHorizontalRule();
0061         if (!frameIt.atEnd()) {
0062             return ++frameIt;
0063         }
0064         return frameIt;
0065     }
0066 
0067     auto it = block.begin();
0068 
0069     // The beginning is the end. This is an empty block. Insert a newline and
0070     // move
0071     // on.
0072     if (it.atEnd()) {
0073         m_builder->addSingleBreakLine();
0074 
0075         if (!frameIt.atEnd()) {
0076             return ++frameIt;
0077         }
0078         return frameIt;
0079     }
0080 
0081     // Don't have p tags inside li tags.
0082     if (!block.textList()) {
0083         // Laurent : we need this margin as it's necessary to show blockquote
0084 
0085         // Don't instruct builders to use margins. The rich text widget doesn't
0086         // have
0087         // an action for them yet,
0088         // So users can't edit them. See bug
0089         // https://bugs.kde.org/show_bug.cgi?id=160600
0090         m_builder->beginParagraph(blockAlignment,
0091                                   blockFormat.topMargin(),
0092                                   blockFormat.bottomMargin(),
0093                                   blockFormat.leftMargin(),
0094                                   blockFormat.rightMargin(),
0095                                   leftToRightText);
0096     }
0097 
0098     while (!it.atEnd()) {
0099         it = processFragment(it, it.fragment(), block.document());
0100     }
0101     // Don't have p tags inside li tags.
0102     if (!block.textList()) {
0103         m_builder->endParagraph();
0104     }
0105 
0106     if (!frameIt.atEnd()) {
0107         return ++frameIt;
0108     }
0109     return frameIt;
0110 }
0111 
0112 QTextBlock::iterator MarkupDirector::processFragment(QTextBlock::iterator it, const QTextFragment &fragment, QTextDocument const *doc)
0113 {
0114     // Same code as Grantlee + a fix !
0115 
0116     //   Q_D( MarkupDirector );
0117     const auto charFormat = fragment.charFormat();
0118     // Need to check if it's a image format.
0119     if (charFormat.isImageFormat()) {
0120         const auto imageFormat = charFormat.toImageFormat();
0121         return processImage(it, imageFormat, doc);
0122     }
0123 
0124     if (charFormat.objectType() >= QTextFormat::UserObject) {
0125         processCustomFragment(fragment, doc);
0126         if (!it.atEnd()) {
0127             return ++it;
0128         }
0129         return it;
0130     }
0131 
0132     auto textObject = doc->objectForFormat(charFormat);
0133     if (textObject) {
0134         return processCharTextObject(it, fragment, textObject);
0135     }
0136 
0137     const auto textStr = fragment.text();
0138     if (textStr.at(0).category() == QChar::Separator_Line) {
0139         m_builder->addSingleBreakLine();
0140         QString t;
0141         for (int i = 1; i < textStr.length(); ++i) {
0142             if (fragment.text().at(i).category() == QChar::Separator_Line) {
0143                 m_builder->appendLiteralText(t);
0144                 if (i < textStr.length() - 1) { // Don't add \n when we have the last char
0145                     m_builder->addSingleBreakLine();
0146                 }
0147                 t.clear();
0148             } else {
0149                 t += fragment.text().at(i);
0150             }
0151         }
0152         if (!t.isEmpty()) {
0153             m_builder->appendLiteralText(t);
0154         }
0155 
0156         if (!it.atEnd()) {
0157             return ++it;
0158         }
0159         return it;
0160     }
0161 
0162     // The order of closing and opening tags can determine whether generated
0163     // html
0164     // is valid or not.
0165     // When processing a document with formatting which appears as
0166     // '<b><i>Some</i>
0167     // formatted<b> text',
0168     // the correct generated output will contain '<strong><em>Some</em>
0169     // formatted<strong> text'.
0170     // However, processing text which appears as '<i><b>Some</b> formatted<i>
0171     // text' might be incorrectly rendered
0172     // as '<strong><em>Some</strong> formatted</em> text' if tags which start at
0173     // the same fragment are
0174     // opened out of order. Here, tags are not nested properly, and the html
0175     // would
0176     // not be valid or render correctly by unforgiving parsers (like QTextEdit).
0177     // One solution is to make the order of opening tags dynamic. In the above
0178     // case, the em tag would
0179     // be opened before the strong tag '<em><strong>Some</strong> formatted</em>
0180     // text'. That would
0181     // require knowledge of which tag is going to close first. That might be
0182     // possible by examining
0183     // the 'next' QTextFragment while processing one.
0184     //
0185     // The other option is to do pessimistic closing of tags.
0186     // In the above case, this means that if a fragment has two or more formats
0187     // applied (bold and italic here),
0188     // and one of them is closed, then all tags should be closed first. They
0189     // will
0190     // of course be reopened
0191     // if necessary while processing the next fragment.
0192     // The above case would be rendered as '<strong><em>Some</em></strong><em>
0193     // formatted</em> text'.
0194     //
0195     // The first option is taken here, as the redundant opening and closing tags
0196     // in the second option
0197     // didn't appeal.
0198     // See testDoubleStartDifferentFinish,
0199     // testDoubleStartDifferentFinishReverseOrder
0200 
0201     processOpeningElements(it);
0202 
0203     // If a sequence such as '<br /><br />' is imported into a document with
0204     // setHtml, LineSeparator
0205     // characters are inserted. Here I make sure to put them back.
0206     auto sl = fragment.text().split(QChar(QChar::LineSeparator));
0207     QStringListIterator i(sl);
0208     auto paraClosed = false;
0209     while (i.hasNext()) {
0210         m_builder->appendLiteralText(i.next());
0211         if (i.hasNext()) {
0212             if (i.peekNext().isEmpty()) {
0213                 if (!paraClosed) {
0214                     m_builder->endParagraph();
0215                     paraClosed = true;
0216                 }
0217                 m_builder->addNewline();
0218             } else if (paraClosed) {
0219                 m_builder->beginParagraph(/* blockAlignment */);
0220                 paraClosed = false;
0221             } else {
0222                 // Bug fixing : add missing single break line
0223                 m_builder->addSingleBreakLine();
0224             }
0225         }
0226     }
0227     if (!it.atEnd()) {
0228         ++it;
0229     }
0230 
0231     processClosingElements(it);
0232 
0233     return it;
0234 }
0235 
0236 void MarkupDirector::processDocumentContents(QTextFrame::iterator start, const QTextFrame::iterator &end)
0237 {
0238     while (!start.atEnd() && start != end) {
0239         auto frame = start.currentFrame();
0240         if (frame) {
0241             auto table = qobject_cast<QTextTable *>(frame);
0242             if (table) {
0243                 start = processTable(start, table);
0244             } else {
0245                 start = processFrame(start, frame);
0246             }
0247         } else {
0248             auto block = start.currentBlock();
0249             Q_ASSERT(block.isValid());
0250             start = processBlock(start, block);
0251         }
0252     }
0253 }
0254 
0255 QTextFrame::iterator MarkupDirector::processFrame(QTextFrame::iterator it, QTextFrame *frame)
0256 {
0257     if (frame) {
0258         processDocumentContents(frame->begin(), frame->end());
0259     }
0260     if (!it.atEnd()) {
0261         return ++it;
0262     }
0263     return it;
0264 }
0265 
0266 QTextFrame::iterator MarkupDirector::processBlock(QTextFrame::iterator it, const QTextBlock &block)
0267 {
0268     if (block.isValid()) {
0269         auto fmt = block.blockFormat();
0270         auto object = block.document()->objectForFormat(fmt);
0271         if (object) {
0272             return processObject(it, block, object);
0273         } else {
0274             return processBlockContents(it, block);
0275         }
0276     }
0277 
0278     if (!it.atEnd()) {
0279         return ++it;
0280     }
0281     return it;
0282 }
0283 
0284 QTextFrame::iterator MarkupDirector::processTable(QTextFrame::iterator it, QTextTable *table)
0285 {
0286     const auto format = table->format();
0287 
0288     const auto colLengths = format.columnWidthConstraints();
0289 
0290     const auto tableWidth = format.width();
0291     QString sWidth;
0292 
0293     if (tableWidth.type() == QTextLength::PercentageLength) {
0294         sWidth = QStringLiteral("%1%");
0295         sWidth = sWidth.arg(tableWidth.rawValue());
0296     } else if (tableWidth.type() == QTextLength::FixedLength) {
0297         sWidth = QStringLiteral("%1");
0298         sWidth = sWidth.arg(tableWidth.rawValue());
0299     }
0300 
0301     m_builder->beginTable(format.cellPadding(), format.cellSpacing(), sWidth);
0302 
0303     const auto headerRowCount = format.headerRowCount();
0304 
0305     QList<QTextTableCell> alreadyProcessedCells;
0306 
0307     for (int row = 0, total = table->rows(); row < total; ++row) {
0308         // Put a thead element around here somewhere?
0309         // if (row < headerRowCount)
0310         // {
0311         // beginTableHeader();
0312         // }
0313 
0314         m_builder->beginTableRow();
0315 
0316         // Header attribute should really be on cells, not determined by number
0317         // of
0318         // rows.
0319         // http://www.webdesignfromscratch.com/html-tables.cfm
0320 
0321         for (int column = 0, total = table->columns(); column < total; ++column) {
0322             auto tableCell = table->cellAt(row, column);
0323 
0324             auto columnSpan = tableCell.columnSpan();
0325             auto rowSpan = tableCell.rowSpan();
0326             if ((rowSpan > 1) || (columnSpan > 1)) {
0327                 if (alreadyProcessedCells.contains(tableCell)) {
0328                     // Already processed this cell. Move on.
0329                     continue;
0330                 } else {
0331                     alreadyProcessedCells.append(tableCell);
0332                 }
0333             }
0334 
0335             auto cellWidth = colLengths.at(column);
0336 
0337             QString sCellWidth;
0338 
0339             if (cellWidth.type() == QTextLength::PercentageLength) {
0340                 sCellWidth = QStringLiteral("%1%").arg(cellWidth.rawValue());
0341             } else if (cellWidth.type() == QTextLength::FixedLength) {
0342                 sCellWidth = QStringLiteral("%1").arg(cellWidth.rawValue());
0343             }
0344 
0345             // TODO: Use THEAD instead
0346             if (row < headerRowCount) {
0347                 m_builder->beginTableHeaderCell(sCellWidth, columnSpan, rowSpan);
0348             } else {
0349                 m_builder->beginTableCell(sCellWidth, columnSpan, rowSpan);
0350             }
0351 
0352             processTableCell(tableCell, table);
0353 
0354             if (row < headerRowCount) {
0355                 m_builder->endTableHeaderCell();
0356             } else {
0357                 m_builder->endTableCell();
0358             }
0359         }
0360         m_builder->endTableRow();
0361     }
0362     m_builder->endTable();
0363 
0364     if (!it.atEnd()) {
0365         return ++it;
0366     }
0367     return it;
0368 }
0369 
0370 void MarkupDirector::processTableCell(const QTextTableCell &tableCell, QTextTable *table)
0371 {
0372     Q_UNUSED(table)
0373     processDocumentContents(tableCell.begin(), tableCell.end());
0374 }
0375 
0376 QPair<QTextFrame::iterator, QTextBlock> MarkupDirector::processList(QTextFrame::iterator it, const QTextBlock &_block, QTextList *list)
0377 {
0378     const auto style = list->format().style();
0379     m_builder->beginList(style);
0380     auto block = _block;
0381     while (block.isValid() && block.textList()) {
0382         m_builder->beginListItem();
0383         processBlockContents(it, block);
0384         m_builder->endListItem();
0385 
0386         if (!it.atEnd()) {
0387             ++it;
0388         }
0389         block = block.next();
0390         if (block.isValid()) {
0391             auto obj = block.document()->objectForFormat(block.blockFormat());
0392             auto group = qobject_cast<QTextBlockGroup *>(obj);
0393             if (group && group != list) {
0394                 auto pair = processBlockGroup(it, block, group);
0395                 it = pair.first;
0396                 block = pair.second;
0397             }
0398         }
0399     }
0400     m_builder->endList();
0401     return qMakePair(it, block);
0402 }
0403 
0404 void MarkupDirector::processCustomFragment(const QTextFragment &fragment, const QTextDocument *doc)
0405 {
0406     Q_UNUSED(fragment)
0407     Q_UNUSED(doc)
0408 }
0409 
0410 QTextFrame::iterator MarkupDirector::processObject(QTextFrame::iterator it, const QTextBlock &block, QTextObject *object)
0411 {
0412     auto group = qobject_cast<QTextBlockGroup *>(object);
0413     if (group) {
0414         return processBlockGroup(it, block, group).first;
0415     }
0416     if (!it.atEnd()) {
0417         return ++it;
0418     }
0419     return it;
0420 }
0421 
0422 QPair<QTextFrame::iterator, QTextBlock> MarkupDirector::skipBlockGroup(QTextFrame::iterator it, const QTextBlock &_block, QTextBlockGroup *blockGroup)
0423 {
0424     auto block = _block;
0425     auto lastBlock = _block;
0426     auto lastIt = it;
0427     auto obj = block.document()->objectForFormat(block.blockFormat());
0428     QTextBlockGroup *nextGroup;
0429 
0430     if (!obj) {
0431         return qMakePair(lastIt, lastBlock);
0432     }
0433 
0434     auto group = qobject_cast<QTextBlockGroup *>(obj);
0435     if (!group) {
0436         return qMakePair(lastIt, lastBlock);
0437     }
0438 
0439     while (block.isValid()) {
0440         if (!group) {
0441             break;
0442         }
0443 
0444         block = block.next();
0445         if (!it.atEnd()) {
0446             ++it;
0447         }
0448 
0449         obj = block.document()->objectForFormat(block.blockFormat());
0450         if (obj) {
0451             continue;
0452         }
0453 
0454         nextGroup = qobject_cast<QTextBlockGroup *>(obj);
0455 
0456         if (group == blockGroup || !nextGroup) {
0457             lastBlock = block;
0458             lastIt = it;
0459         }
0460         group = nextGroup;
0461     }
0462     return qMakePair(lastIt, lastBlock);
0463 }
0464 
0465 QPair<QTextFrame::iterator, QTextBlock> MarkupDirector::processBlockGroup(const QTextFrame::iterator &it, const QTextBlock &block, QTextBlockGroup *blockGroup)
0466 {
0467     const auto list = qobject_cast<QTextList *>(blockGroup);
0468     if (list) {
0469         return processList(it, block, list);
0470     }
0471     return skipBlockGroup(it, block, blockGroup);
0472 }
0473 
0474 void MarkupDirector::processDocument(QTextDocument *doc)
0475 {
0476     processFrame(QTextFrame::iterator(), doc->rootFrame());
0477 }
0478 
0479 QTextBlock::iterator MarkupDirector::processCharTextObject(QTextBlock::iterator it, const QTextFragment &fragment, QTextObject *textObject)
0480 {
0481     const auto fragmentFormat = fragment.charFormat();
0482     if (fragmentFormat.isImageFormat()) {
0483         const auto imageFormat = fragmentFormat.toImageFormat();
0484         return processImage(it, imageFormat, textObject->document());
0485     }
0486     if (!it.atEnd()) {
0487         return ++it;
0488     }
0489     return it;
0490 }
0491 
0492 QTextBlock::iterator MarkupDirector::processImage(QTextBlock::iterator it, const QTextImageFormat &imageFormat, const QTextDocument *doc)
0493 {
0494     Q_UNUSED(doc)
0495     // TODO: Close any open format elements?
0496     m_builder->insertImage(imageFormat.name(), imageFormat.width(), imageFormat.height());
0497     if (!it.atEnd()) {
0498         return ++it;
0499     }
0500     return it;
0501 }
0502 
0503 void MarkupDirector::processClosingElements(const QTextBlock::iterator &it)
0504 {
0505     Q_D(MarkupDirector);
0506     // The order of closing elements is determined by the order they were opened
0507     // in.
0508     // The order of opened elements is in the openElements member list.
0509     // see testDifferentStartDoubleFinish and
0510     // testDifferentStartDoubleFinishReverseOrder
0511 
0512     if (d->m_openElements.isEmpty()) {
0513         return;
0514     }
0515 
0516     auto elementsToClose = getElementsToClose(it);
0517 
0518     int previousSize;
0519     auto remainingSize = elementsToClose.size();
0520     while (!elementsToClose.isEmpty()) {
0521         auto tag = d->m_openElements.last();
0522         if (elementsToClose.contains(tag)) {
0523             switch (tag) {
0524             case Strong:
0525                 m_builder->endStrong();
0526                 break;
0527             case Emph:
0528                 m_builder->endEmph();
0529                 break;
0530             case Underline:
0531                 m_builder->endUnderline();
0532                 break;
0533             case StrikeOut:
0534                 m_builder->endStrikeout();
0535                 break;
0536             case SpanFontPointSize:
0537                 m_builder->endFontPointSize();
0538                 break;
0539             case SpanFontFamily:
0540                 m_builder->endFontFamily();
0541                 break;
0542             case SpanBackground:
0543                 m_builder->endBackground();
0544                 // Clear background otherwise if we select 2 lines with same color it will not applied. bug #442416
0545                 d->m_openBackground = {};
0546                 break;
0547             case SpanForeground:
0548                 m_builder->endForeground();
0549                 // Clear foreground text color otherwise if we select 2 lines with same text color it will not applied. bug #442416
0550                 d->m_openForeground = {};
0551                 break;
0552             case Anchor:
0553                 m_builder->endAnchor();
0554                 break;
0555             case SubScript:
0556                 m_builder->endSubscript();
0557                 break;
0558             case SuperScript:
0559                 m_builder->endSuperscript();
0560                 break;
0561 
0562             default:
0563                 break;
0564             }
0565             d->m_openElements.removeLast();
0566             elementsToClose.remove(tag);
0567         }
0568         previousSize = remainingSize;
0569         remainingSize = elementsToClose.size();
0570 
0571         if (previousSize == remainingSize) {
0572             // Iterated once through without closing any tags.
0573             // This means that there's overlap in the tags, such as
0574             // 'text with <b>some <i>formatting</i></b><i> tags</i>'
0575             // See testOverlap.
0576             // The top element in openElements must be a blocker, so close it on
0577             // next
0578             // iteration.
0579             elementsToClose.insert(d->m_openElements.last());
0580         }
0581     }
0582 }
0583 
0584 void MarkupDirector::processOpeningElements(const QTextBlock::iterator &it)
0585 {
0586     Q_D(MarkupDirector);
0587     auto fragment = it.fragment();
0588 
0589     if (!fragment.isValid()) {
0590         return;
0591     }
0592 
0593     const auto fragmentFormat = fragment.charFormat();
0594     const auto elementsToOpenList = getElementsToOpen(it);
0595 
0596     for (int tag : elementsToOpenList) {
0597         switch (tag) {
0598         case Strong:
0599             m_builder->beginStrong();
0600             break;
0601         case Emph:
0602             m_builder->beginEmph();
0603             break;
0604         case Underline:
0605             m_builder->beginUnderline();
0606             break;
0607         case StrikeOut:
0608             m_builder->beginStrikeout();
0609             break;
0610         case SpanFontPointSize:
0611             d->m_openFontPointSize = fragmentFormat.font().pointSize();
0612             m_builder->beginFontPointSize(d->m_openFontPointSize);
0613             break;
0614         case SpanFontFamily:
0615             d->m_openFontFamily =
0616                 fragmentFormat.fontFamilies().toStringList().isEmpty() ? QString() : fragmentFormat.fontFamilies().toStringList().constFirst();
0617             m_builder->beginFontFamily(d->m_openFontFamily);
0618             break;
0619         case SpanBackground:
0620             d->m_openBackground = fragmentFormat.background();
0621             m_builder->beginBackground(d->m_openBackground);
0622             break;
0623         case SpanForeground:
0624             d->m_openForeground = fragmentFormat.foreground();
0625             m_builder->beginForeground(d->m_openForeground);
0626             break;
0627         case Anchor: {
0628             // TODO: Multiple anchor names here.
0629             auto anchorNames = fragmentFormat.anchorNames();
0630             if (!anchorNames.isEmpty()) {
0631                 while (!anchorNames.isEmpty()) {
0632                     auto n = anchorNames.last();
0633                     anchorNames.removeLast();
0634                     if (anchorNames.isEmpty()) {
0635                         // Doesn't matter if anchorHref is empty.
0636                         m_builder->beginAnchor(fragmentFormat.anchorHref(), n);
0637                         break;
0638                     } else {
0639                         // Empty <a> tags allow multiple names for the same
0640                         // section.
0641                         m_builder->beginAnchor(QString(), n);
0642                         m_builder->endAnchor();
0643                     }
0644                 }
0645             } else {
0646                 m_builder->beginAnchor(fragmentFormat.anchorHref());
0647             }
0648             d->m_openAnchorHref = fragmentFormat.anchorHref();
0649             break;
0650         }
0651         case SuperScript:
0652             m_builder->beginSuperscript();
0653             break;
0654         case SubScript:
0655             m_builder->beginSubscript();
0656             break;
0657         default:
0658             break;
0659         }
0660         d->m_openElements.append(tag);
0661         d->m_elementsToOpen.remove(tag);
0662     }
0663 }
0664 
0665 QSet<int> MarkupDirector::getElementsToClose(const QTextBlock::iterator &it) const
0666 {
0667     Q_D(const MarkupDirector);
0668     QSet<int> closedElements;
0669 
0670     if (it.atEnd()) {
0671         // End of block?. Close all open tags.
0672 
0673         const QList<int> openElement = d->m_openElements;
0674         auto elementsToClose = QSet<int>(openElement.begin(), openElement.end());
0675         return elementsToClose.unite(d->m_elementsToOpen);
0676     }
0677 
0678     auto fragment = it.fragment();
0679 
0680     if (!fragment.isValid()) {
0681         return closedElements;
0682     }
0683 
0684     const auto fragmentFormat = fragment.charFormat();
0685     const auto fontWeight = fragmentFormat.fontWeight();
0686     const auto fontItalic = fragmentFormat.fontItalic();
0687     const auto fontUnderline = fragmentFormat.fontUnderline();
0688     const auto fontStrikeout = fragmentFormat.fontStrikeOut();
0689 
0690     const auto fontForeground = fragmentFormat.foreground();
0691     const auto fontBackground = fragmentFormat.background();
0692     const QStringList families = fragmentFormat.fontFamilies().toStringList();
0693     const auto fontFamily = families.isEmpty() ? QString() : families.constFirst();
0694     const auto fontPointSize = fragmentFormat.font().pointSize();
0695     const auto anchorHref = fragmentFormat.anchorHref();
0696 
0697     const auto vAlign = fragmentFormat.verticalAlignment();
0698     const auto superscript = (vAlign == QTextCharFormat::AlignSuperScript);
0699     const auto subscript = (vAlign == QTextCharFormat::AlignSubScript);
0700 
0701     if (!fontStrikeout && (d->m_openElements.contains(StrikeOut) || d->m_elementsToOpen.contains(StrikeOut))) {
0702         closedElements.insert(StrikeOut);
0703     }
0704 
0705     if (!fontUnderline && (d->m_openElements.contains(Underline) || d->m_elementsToOpen.contains(Underline))
0706         && !(d->m_openElements.contains(Anchor) || d->m_elementsToOpen.contains(Anchor))) {
0707         closedElements.insert(Underline);
0708     }
0709 
0710     if (!fontItalic && (d->m_openElements.contains(Emph) || d->m_elementsToOpen.contains(Emph))) {
0711         closedElements.insert(Emph);
0712     }
0713 
0714     if (fontWeight != QFont::Bold && (d->m_openElements.contains(Strong) || d->m_elementsToOpen.contains(Strong))) {
0715         closedElements.insert(Strong);
0716     }
0717 
0718     if ((d->m_openElements.contains(SpanFontPointSize) || d->m_elementsToOpen.contains(SpanFontPointSize)) && (d->m_openFontPointSize != fontPointSize)) {
0719         closedElements.insert(SpanFontPointSize);
0720     }
0721 
0722     if ((d->m_openElements.contains(SpanFontFamily) || d->m_elementsToOpen.contains(SpanFontFamily)) && (d->m_openFontFamily != fontFamily)) {
0723         closedElements.insert(SpanFontFamily);
0724     }
0725 
0726     if ((d->m_openElements.contains(SpanBackground) && (d->m_openBackground != fontBackground))
0727         || (d->m_elementsToOpen.contains(SpanBackground) && (d->m_backgroundToOpen != fontBackground))) {
0728         closedElements.insert(SpanBackground);
0729     }
0730 
0731     if ((d->m_openElements.contains(SpanForeground) && (d->m_openForeground != fontForeground))
0732         || (d->m_elementsToOpen.contains(SpanForeground) && (d->m_foregroundToOpen != fontForeground))) {
0733         closedElements.insert(SpanForeground);
0734     }
0735 
0736     if ((d->m_openElements.contains(Anchor) && (d->m_openAnchorHref != anchorHref))
0737         || (d->m_elementsToOpen.contains(Anchor) && (d->m_anchorHrefToOpen != anchorHref))) {
0738         closedElements.insert(Anchor);
0739     }
0740 
0741     if (!subscript && (d->m_openElements.contains(SubScript) || d->m_elementsToOpen.contains(SubScript))) {
0742         closedElements.insert(SubScript);
0743     }
0744 
0745     if (!superscript && (d->m_openElements.contains(SuperScript) || d->m_elementsToOpen.contains(SuperScript))) {
0746         closedElements.insert(SuperScript);
0747     }
0748     return closedElements;
0749 }
0750 
0751 QList<int> MarkupDirector::getElementsToOpen(const QTextBlock::iterator &it)
0752 {
0753     Q_D(MarkupDirector);
0754     auto fragment = it.fragment();
0755     if (!fragment.isValid()) {
0756         return {};
0757     }
0758     const auto fragmentFormat = fragment.charFormat();
0759 
0760     const auto fontWeight = fragmentFormat.fontWeight();
0761     const auto fontItalic = fragmentFormat.fontItalic();
0762     const auto fontUnderline = fragmentFormat.fontUnderline();
0763     const auto fontStrikeout = fragmentFormat.fontStrikeOut();
0764 
0765     const auto fontForeground = fragmentFormat.foreground();
0766     const auto fontBackground = fragmentFormat.background();
0767 
0768     const QStringList families = fragmentFormat.fontFamilies().toStringList();
0769     const auto fontFamily = families.isEmpty() ? QString() : families.constFirst();
0770     const auto fontPointSize = fragmentFormat.font().pointSize();
0771     const auto anchorHref = fragmentFormat.anchorHref();
0772 
0773     const auto vAlign = fragmentFormat.verticalAlignment();
0774     const auto superscript = (vAlign == QTextCharFormat::AlignSuperScript);
0775     const auto subscript = (vAlign == QTextCharFormat::AlignSubScript);
0776 
0777     if (superscript && !(d->m_openElements.contains(SuperScript))) {
0778         d->m_elementsToOpen.insert(SuperScript);
0779     }
0780 
0781     if (subscript && !(d->m_openElements.contains(SubScript))) {
0782         d->m_elementsToOpen.insert(SubScript);
0783     }
0784 
0785     if (!anchorHref.isEmpty() && !(d->m_openElements.contains(Anchor)) && (d->m_openAnchorHref != anchorHref)) {
0786         d->m_elementsToOpen.insert(Anchor);
0787         d->m_anchorHrefToOpen = anchorHref;
0788     }
0789 
0790     if (fontForeground != Qt::NoBrush
0791         && !(d->m_openElements.contains(SpanForeground)) // Can only open one
0792         // foreground element
0793         // at a time.
0794         && (fontForeground != d->m_openForeground)
0795         && !((d->m_openElements.contains(Anchor) // Links can't have a foreground color.
0796               || d->m_elementsToOpen.contains(Anchor)))) {
0797         d->m_elementsToOpen.insert(SpanForeground);
0798         d->m_foregroundToOpen = fontForeground;
0799     }
0800 
0801     if (fontBackground != Qt::NoBrush && !(d->m_openElements.contains(SpanBackground)) && (fontBackground != d->m_openBackground)) {
0802         d->m_elementsToOpen.insert(SpanBackground);
0803         d->m_backgroundToOpen = fontBackground;
0804     }
0805 
0806     if (!fontFamily.isEmpty() && !(d->m_openElements.contains(SpanFontFamily)) && (fontFamily != d->m_openFontFamily)) {
0807         d->m_elementsToOpen.insert(SpanFontFamily);
0808         d->m_fontFamilyToOpen = fontFamily;
0809     }
0810 
0811     if ((QTextCharFormat().font().pointSize() != fontPointSize) // Different from the default.
0812         && !(d->m_openElements.contains(SpanFontPointSize)) && (fontPointSize != d->m_openFontPointSize)) {
0813         d->m_elementsToOpen.insert(SpanFontPointSize);
0814         d->m_fontPointSizeToOpen = fontPointSize;
0815     }
0816 
0817     //   Only open a new bold tag if one is not already open.
0818     //   eg, <b>some <i>mixed</i> format</b> should be as is, rather than
0819     //   <b>some </b><b><i>mixed</i></b><b> format</b>
0820 
0821     if (fontWeight >= QFont::DemiBold && !(d->m_openElements.contains(Strong))) {
0822         d->m_elementsToOpen.insert(Strong);
0823     }
0824 
0825     if (fontItalic && !(d->m_openElements.contains(Emph))) {
0826         d->m_elementsToOpen.insert(Emph);
0827     }
0828 
0829     if (fontUnderline && !(d->m_openElements.contains(Underline))
0830         && !(d->m_openElements.contains(Anchor) || d->m_elementsToOpen.contains(Anchor)) // Can't change the underline state of a link.
0831     ) {
0832         d->m_elementsToOpen.insert(Underline);
0833     }
0834 
0835     if (fontStrikeout && !(d->m_openElements.contains(StrikeOut))) {
0836         d->m_elementsToOpen.insert(StrikeOut);
0837     }
0838 
0839     if (d->m_elementsToOpen.size() <= 1) {
0840         return d->m_elementsToOpen.values();
0841     }
0842     return sortOpeningOrder(d->m_elementsToOpen, it);
0843 }
0844 
0845 QList<int> MarkupDirector::sortOpeningOrder(QSet<int> openingOrder, QTextBlock::iterator it) const
0846 {
0847     QList<int> sortedOpenedElements;
0848 
0849     // This is an insertion sort in a way. elements in openingOrder are assumed
0850     // to
0851     // be out of order.
0852     // The rest of the block is traversed until there are no more elements to
0853     // sort, or the end is reached.
0854     while (!openingOrder.isEmpty()) {
0855         if (!it.atEnd()) {
0856             it++;
0857 
0858             if (!it.atEnd()) {
0859                 // Because I've iterated, this returns the elements that will
0860                 // be closed by the next fragment.
0861                 const auto elementsToClose = getElementsToClose(it);
0862 
0863                 // The exact order these are opened in is irrelevant, as all
0864                 // will be
0865                 // closed on the same block.
0866                 // See testDoubleFormat.
0867                 for (int tag : elementsToClose) {
0868                     if (openingOrder.remove(tag)) {
0869                         sortedOpenedElements.prepend(tag);
0870                     }
0871                 }
0872             }
0873         } else {
0874             // End of block. Need to close all open elements.
0875             // Order irrelevant in this case.
0876             for (int tag : std::as_const(openingOrder)) {
0877                 sortedOpenedElements.prepend(tag);
0878             }
0879             break;
0880         }
0881     }
0882     return sortedOpenedElements;
0883 }