Files
QtHtmlEditor/src/richtexteditor.cpp
2023-11-15 20:54:49 +01:00

180 lines
5.1 KiB
C++

#include "richtexteditor.h"
#include "richtexteditortoolbar.h"
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QStringView>
#include <QStringLiteral>
#include <algorithm>
const bool simplifyRichTextDefault = true;
using namespace Qt::StringLiterals;
// Richtext simplification filter helpers: Elements to be discarded
static inline bool filterElement(QStringView name)
{
return name != "meta"_L1 && name != "style"_L1;
}
// Richtext simplification filter helpers: Filter attributes of elements
static inline void filterAttributes(QStringView name,
QXmlStreamAttributes *atts,
bool *paragraphAlignmentFound)
{
if (atts->isEmpty())
return;
// No style attributes for <body>
if (name == "body"_L1) {
atts->clear();
return;
}
// Clean out everything except 'align' for 'p'
if (name == "p"_L1) {
for (auto it = atts->begin(); it != atts->end(); ) {
if (it->name() == "align"_L1) {
++it;
*paragraphAlignmentFound = true;
} else {
it = atts->erase(it);
}
}
return;
}
}
// Richtext simplification filter helpers: Check for blank QStringView.
static inline bool isWhiteSpace(QStringView in)
{
return std::all_of(in.cbegin(), in.cend(),
[](QChar c) { return c.isSpace(); });
}
// Richtext simplification filter: Remove hard-coded font settings,
// <style> elements, <p> attributes other than 'align' and
// and unnecessary meta-information.
QString simplifyRichTextFilter(const QString &in, bool *isPlainTextPtr = nullptr)
{
unsigned elementCount = 0;
bool paragraphAlignmentFound = false;
QString out;
QXmlStreamReader reader(in);
QXmlStreamWriter writer(&out);
writer.setAutoFormatting(false);
writer.setAutoFormattingIndent(0);
while (!reader.atEnd()) {
switch (reader.readNext()) {
case QXmlStreamReader::StartElement:
elementCount++;
if (filterElement(reader.name())) {
const auto name = reader.name();
QXmlStreamAttributes attributes = reader.attributes();
filterAttributes(name, &attributes, &paragraphAlignmentFound);
writer.writeStartElement(name.toString());
if (!attributes.isEmpty())
writer.writeAttributes(attributes);
} else {
reader.readElementText(); // Skip away all nested elements and characters.
}
break;
case QXmlStreamReader::Characters:
if (!isWhiteSpace(reader.text()))
writer.writeCharacters(reader.text().toString());
break;
case QXmlStreamReader::EndElement:
writer.writeEndElement();
break;
default:
break;
}
}
// Check for plain text (no spans, just <html><head><body><p>)
if (isPlainTextPtr)
*isPlainTextPtr = !paragraphAlignmentFound && elementCount == 4u; //
return out;
}
RichTextEditor::RichTextEditor(QWidget *parent)
: QTextEdit(parent), m_simplifyRichText(simplifyRichTextDefault)
{
connect(this, &RichTextEditor::currentCharFormatChanged,
this, &RichTextEditor::stateChanged);
connect(this, &RichTextEditor::cursorPositionChanged,
this, &RichTextEditor::stateChanged);
}
QToolBar *RichTextEditor::createToolBar(QWidget *parent)
{
return new RichTextEditorToolBar(this, parent);
}
void RichTextEditor::setFontBold(bool b)
{
if (b)
setFontWeight(QFont::Bold);
else
setFontWeight(QFont::Normal);
}
void RichTextEditor::setFontPointSize(double d)
{
QTextEdit::setFontPointSize(qreal(d));
}
void RichTextEditor::setText(const QString &text)
{
if (Qt::mightBeRichText(text))
setHtml(text);
else
setPlainText(text);
}
void RichTextEditor::setSimplifyRichText(bool v)
{
if (v != m_simplifyRichText) {
m_simplifyRichText = v;
emit simplifyRichTextChanged(v);
}
}
void RichTextEditor::setDefaultFont(QFont font)
{
// Some default fonts on Windows have a default size of 7.8,
// which results in complicated rich text generated by toHtml().
// Use an integer value.
const int pointSize = qRound(font.pointSizeF());
if (pointSize > 0 && !qFuzzyCompare(qreal(pointSize), font.pointSizeF())) {
font.setPointSize(pointSize);
}
document()->setDefaultFont(font);
if (font.pointSize() > 0)
setFontPointSize(font.pointSize());
else
setFontPointSize(QFontInfo(font).pointSize());
emit textChanged();
}
QString RichTextEditor::text(Qt::TextFormat format) const
{
switch (format) {
case Qt::PlainText:
return toPlainText();
case Qt::RichText:
return m_simplifyRichText ? simplifyRichTextFilter(toHtml()) : toHtml();
default:
break;
}
const QString html = toHtml();
bool isPlainText;
const QString simplifiedHtml = simplifyRichTextFilter(html, &isPlainText);
if (isPlainText)
return toPlainText();
return m_simplifyRichText ? simplifiedHtml : html;
}