File indexing completed on 2024-05-12 15:50:05
0001 /* 0002 SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org> 0003 SPDX-FileCopyrightText: 2018 Christoph Cullmann <cullmann@kde.org> 0004 0005 SPDX-License-Identifier: MIT 0006 */ 0007 0008 #include "htmlhighlighter.h" 0009 #include "definition.h" 0010 #include "format.h" 0011 #include "ksyntaxhighlighting_logging.h" 0012 #include "state.h" 0013 #include "theme.h" 0014 0015 #include <QFile> 0016 #include <QFileInfo> 0017 #include <QTextStream> 0018 #include <QVarLengthArray> 0019 0020 using namespace KSyntaxHighlighting; 0021 0022 class KSyntaxHighlighting::HtmlHighlighterPrivate 0023 { 0024 public: 0025 std::unique_ptr<QTextStream> out; 0026 std::unique_ptr<QFile> file; 0027 QString currentLine; 0028 }; 0029 0030 HtmlHighlighter::HtmlHighlighter() 0031 : d(new HtmlHighlighterPrivate()) 0032 { 0033 } 0034 0035 HtmlHighlighter::~HtmlHighlighter() 0036 { 0037 } 0038 0039 void HtmlHighlighter::setOutputFile(const QString &fileName) 0040 { 0041 d->file.reset(new QFile(fileName)); 0042 if (!d->file->open(QFile::WriteOnly | QFile::Truncate)) { 0043 qCWarning(Log) << "Failed to open output file" << fileName << ":" << d->file->errorString(); 0044 return; 0045 } 0046 d->out.reset(new QTextStream(d->file.get())); 0047 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0048 d->out->setEncoding(QStringConverter::Utf8); 0049 #else 0050 d->out->setCodec("UTF-8"); 0051 #endif 0052 } 0053 0054 void HtmlHighlighter::setOutputFile(FILE *fileHandle) 0055 { 0056 d->out.reset(new QTextStream(fileHandle, QIODevice::WriteOnly)); 0057 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0058 d->out->setEncoding(QStringConverter::Utf8); 0059 #else 0060 d->out->setCodec("UTF-8"); 0061 #endif 0062 } 0063 0064 void HtmlHighlighter::highlightFile(const QString &fileName, const QString &title) 0065 { 0066 QFileInfo fi(fileName); 0067 QFile f(fileName); 0068 if (!f.open(QFile::ReadOnly)) { 0069 qCWarning(Log) << "Failed to open input file" << fileName << ":" << f.errorString(); 0070 return; 0071 } 0072 0073 if (title.isEmpty()) { 0074 highlightData(&f, fi.fileName()); 0075 } else { 0076 highlightData(&f, title); 0077 } 0078 } 0079 0080 /** 0081 * @brief toHtmlRgba 0082 * Converts QColor -> rgba(r, g, b, a) if there is an alpha channel 0083 * otherwise it will just return the hexcode. This is because QColor 0084 * outputs #AARRGGBB, whereas browser support #RRGGBBAA. 0085 * 0086 * @param color 0087 * @return 0088 */ 0089 static QString toHtmlRgbaString(const QColor &color) 0090 { 0091 if (color.alpha() == 0xFF) { 0092 return color.name(); 0093 } 0094 0095 QString rgba = QStringLiteral("rgba("); 0096 rgba.append(QString::number(color.red())); 0097 rgba.append(QLatin1Char(',')); 0098 rgba.append(QString::number(color.green())); 0099 rgba.append(QLatin1Char(',')); 0100 rgba.append(QString::number(color.blue())); 0101 rgba.append(QLatin1Char(',')); 0102 // this must be alphaF 0103 rgba.append(QString::number(color.alphaF())); 0104 rgba.append(QLatin1Char(')')); 0105 return rgba; 0106 } 0107 0108 void HtmlHighlighter::highlightData(QIODevice *dev, const QString &title) 0109 { 0110 if (!d->out) { 0111 qCWarning(Log) << "No output stream defined!"; 0112 return; 0113 } 0114 0115 QString htmlTitle; 0116 if (title.isEmpty()) { 0117 htmlTitle = QStringLiteral("Kate Syntax Highlighter"); 0118 } else { 0119 htmlTitle = title.toHtmlEscaped(); 0120 } 0121 0122 State state; 0123 *d->out << "<!DOCTYPE html>\n"; 0124 *d->out << "<html><head>\n"; 0125 *d->out << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n"; 0126 *d->out << "<title>" << htmlTitle << "</title>\n"; 0127 *d->out << "<meta name=\"generator\" content=\"KF5::SyntaxHighlighting - Definition (" << definition().name() << ") - Theme (" << theme().name() 0128 << ")\"/>\n"; 0129 *d->out << "</head><body"; 0130 *d->out << " style=\"background-color:" << toHtmlRgbaString(QColor::fromRgba(theme().editorColor(Theme::BackgroundColor))); 0131 if (theme().textColor(Theme::Normal)) { 0132 *d->out << ";color:" << toHtmlRgbaString(QColor::fromRgba(theme().textColor(Theme::Normal))); 0133 } 0134 *d->out << "\"><pre>\n"; 0135 0136 QTextStream in(dev); 0137 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0138 in.setCodec("UTF-8"); 0139 #endif 0140 while (in.readLineInto(&d->currentLine)) { 0141 state = highlightLine(d->currentLine, state); 0142 *d->out << "\n"; 0143 } 0144 0145 *d->out << "</pre></body></html>\n"; 0146 d->out->flush(); 0147 0148 d->out.reset(); 0149 d->file.reset(); 0150 } 0151 0152 void HtmlHighlighter::applyFormat(int offset, int length, const Format &format) 0153 { 0154 if (length == 0) { 0155 return; 0156 } 0157 0158 // collect potential output, cheaper than thinking about "is there any?" 0159 QVarLengthArray<QString, 16> formatOutput; 0160 if (format.hasTextColor(theme())) { 0161 formatOutput << QStringLiteral("color:") << toHtmlRgbaString(format.textColor(theme())) << QStringLiteral(";"); 0162 } 0163 if (format.hasBackgroundColor(theme())) { 0164 formatOutput << QStringLiteral("background-color:") << toHtmlRgbaString(format.backgroundColor(theme())) << QStringLiteral(";"); 0165 } 0166 if (format.isBold(theme())) { 0167 formatOutput << QStringLiteral("font-weight:bold;"); 0168 } 0169 if (format.isItalic(theme())) { 0170 formatOutput << QStringLiteral("font-style:italic;"); 0171 } 0172 if (format.isUnderline(theme())) { 0173 formatOutput << QStringLiteral("text-decoration:underline;"); 0174 } 0175 if (format.isStrikeThrough(theme())) { 0176 formatOutput << QStringLiteral("text-decoration:line-through;"); 0177 } 0178 0179 if (!formatOutput.isEmpty()) { 0180 *d->out << "<span style=\""; 0181 for (const auto &out : std::as_const(formatOutput)) { 0182 *d->out << out; 0183 } 0184 *d->out << "\">"; 0185 } 0186 0187 *d->out << d->currentLine.mid(offset, length).toHtmlEscaped(); 0188 0189 if (!formatOutput.isEmpty()) { 0190 *d->out << "</span>"; 0191 } 0192 }