File indexing completed on 2024-05-12 04:02:19
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 "abstracthighlighter_p.h" 0010 #include "definition.h" 0011 #include "definition_p.h" 0012 #include "format.h" 0013 #include "ksyntaxhighlighting_logging.h" 0014 #include "state.h" 0015 #include "theme.h" 0016 0017 #include <QFile> 0018 #include <QFileInfo> 0019 #include <QIODevice> 0020 #include <QTextStream> 0021 0022 using namespace KSyntaxHighlighting; 0023 0024 class KSyntaxHighlighting::HtmlHighlighterPrivate : public AbstractHighlighterPrivate 0025 { 0026 public: 0027 std::unique_ptr<QTextStream> out; 0028 std::unique_ptr<QFile> file; 0029 QString currentLine; 0030 std::vector<QString> htmlStyles; 0031 }; 0032 0033 HtmlHighlighter::HtmlHighlighter() 0034 : AbstractHighlighter(new HtmlHighlighterPrivate()) 0035 { 0036 } 0037 0038 HtmlHighlighter::~HtmlHighlighter() 0039 { 0040 } 0041 0042 void HtmlHighlighter::setOutputFile(const QString &fileName) 0043 { 0044 Q_D(HtmlHighlighter); 0045 d->file.reset(new QFile(fileName)); 0046 if (!d->file->open(QFile::WriteOnly | QFile::Truncate)) { 0047 qCWarning(Log) << "Failed to open output file" << fileName << ":" << d->file->errorString(); 0048 return; 0049 } 0050 d->out.reset(new QTextStream(d->file.get())); 0051 d->out->setEncoding(QStringConverter::Utf8); 0052 } 0053 0054 void HtmlHighlighter::setOutputFile(FILE *fileHandle) 0055 { 0056 Q_D(HtmlHighlighter); 0057 d->out.reset(new QTextStream(fileHandle, QIODevice::WriteOnly)); 0058 d->out->setEncoding(QStringConverter::Utf8); 0059 } 0060 0061 void HtmlHighlighter::highlightFile(const QString &fileName, const QString &title) 0062 { 0063 QFileInfo fi(fileName); 0064 QFile f(fileName); 0065 if (!f.open(QFile::ReadOnly)) { 0066 qCWarning(Log) << "Failed to open input file" << fileName << ":" << f.errorString(); 0067 return; 0068 } 0069 0070 if (title.isEmpty()) { 0071 highlightData(&f, fi.fileName()); 0072 } else { 0073 highlightData(&f, title); 0074 } 0075 } 0076 0077 /** 0078 * @brief toHtmlRgba 0079 * Converts QColor -> #RRGGBBAA if there is an alpha channel 0080 * otherwise it will just return the hexcode. This is because QColor 0081 * outputs #AARRGGBB, whereas browser support #RRGGBBAA. 0082 * 0083 * @param color 0084 * @return 0085 */ 0086 static QString toHtmlRgbaString(const QColor &color) 0087 { 0088 if (color.alpha() == 0xFF) { 0089 return color.name(); 0090 } 0091 static const char16_t digits[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 0092 QChar hexcode[9]; 0093 hexcode[0] = QLatin1Char('#'); 0094 hexcode[1] = digits[color.red() >> 4]; 0095 hexcode[2] = digits[color.red() & 0xf]; 0096 hexcode[3] = digits[color.green() >> 4]; 0097 hexcode[4] = digits[color.green() & 0xf]; 0098 hexcode[5] = digits[color.blue() >> 4]; 0099 hexcode[6] = digits[color.blue() & 0xf]; 0100 hexcode[7] = digits[color.alpha() >> 4]; 0101 hexcode[8] = digits[color.alpha() & 0xf]; 0102 return QString(hexcode, 9); 0103 } 0104 0105 void HtmlHighlighter::highlightData(QIODevice *dev, const QString &title) 0106 { 0107 Q_D(HtmlHighlighter); 0108 0109 if (!d->out) { 0110 qCWarning(Log) << "No output stream defined!"; 0111 return; 0112 } 0113 0114 QString htmlTitle; 0115 if (title.isEmpty()) { 0116 htmlTitle = QStringLiteral("KSyntaxHighlighter"); 0117 } else { 0118 htmlTitle = title.toHtmlEscaped(); 0119 } 0120 0121 const auto &theme = d->m_theme; 0122 const auto &definition = d->m_definition; 0123 0124 auto definitions = definition.includedDefinitions(); 0125 definitions.append(definition); 0126 0127 int maxId = 0; 0128 for (const auto &definition : std::as_const(definitions)) { 0129 for (const auto &format : std::as_const(DefinitionData::get(definition)->formats)) { 0130 maxId = qMax(maxId, format.id()); 0131 } 0132 } 0133 d->htmlStyles.clear(); 0134 // htmlStyles must not be empty for applyFormat to work even with a definition without any context 0135 d->htmlStyles.resize(maxId + 1); 0136 0137 // initialize htmlStyles 0138 for (const auto &definition : std::as_const(definitions)) { 0139 for (const auto &format : std::as_const(DefinitionData::get(definition)->formats)) { 0140 auto &buffer = d->htmlStyles[format.id()]; 0141 if (format.hasTextColor(theme)) { 0142 buffer += QStringLiteral("color:") + toHtmlRgbaString(format.textColor(theme)) + QStringLiteral(";"); 0143 } 0144 if (format.hasBackgroundColor(theme)) { 0145 buffer += QStringLiteral("background-color:") + toHtmlRgbaString(format.backgroundColor(theme)) + QStringLiteral(";"); 0146 } 0147 if (format.isBold(theme)) { 0148 buffer += QStringLiteral("font-weight:bold;"); 0149 } 0150 if (format.isItalic(theme)) { 0151 buffer += QStringLiteral("font-style:italic;"); 0152 } 0153 if (format.isUnderline(theme)) { 0154 buffer += QStringLiteral("text-decoration:underline;"); 0155 } 0156 if (format.isStrikeThrough(theme)) { 0157 buffer += QStringLiteral("text-decoration:line-through;"); 0158 } 0159 0160 if (!buffer.isEmpty()) { 0161 buffer.insert(0, QStringLiteral("<span style=\"")); 0162 // replace last ';' 0163 buffer.back() = u'"'; 0164 buffer += u'>'; 0165 } 0166 } 0167 } 0168 0169 State state; 0170 *d->out << "<!DOCTYPE html>\n"; 0171 *d->out << "<html><head>\n"; 0172 *d->out << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n"; 0173 *d->out << "<title>" << htmlTitle << "</title>\n"; 0174 *d->out << "<meta name=\"generator\" content=\"KF5::SyntaxHighlighting - Definition (" << definition.name() << ") - Theme (" << theme.name() << ")\"/>\n"; 0175 *d->out << "</head><body"; 0176 *d->out << " style=\"background-color:" << toHtmlRgbaString(QColor::fromRgba(theme.editorColor(Theme::BackgroundColor))); 0177 if (theme.textColor(Theme::Normal)) { 0178 *d->out << ";color:" << toHtmlRgbaString(QColor::fromRgba(theme.textColor(Theme::Normal))); 0179 } 0180 *d->out << "\"><pre>\n"; 0181 0182 QTextStream in(dev); 0183 while (in.readLineInto(&d->currentLine)) { 0184 state = highlightLine(d->currentLine, state); 0185 *d->out << "\n"; 0186 } 0187 0188 *d->out << "</pre></body></html>\n"; 0189 d->out->flush(); 0190 0191 d->out.reset(); 0192 d->file.reset(); 0193 } 0194 0195 void HtmlHighlighter::applyFormat(int offset, int length, const Format &format) 0196 { 0197 if (length == 0) { 0198 return; 0199 } 0200 0201 Q_D(HtmlHighlighter); 0202 0203 auto const &htmlStyle = d->htmlStyles[format.id()]; 0204 0205 if (!htmlStyle.isEmpty()) { 0206 *d->out << htmlStyle; 0207 } 0208 0209 for (QChar ch : QStringView(d->currentLine).mid(offset, length)) { 0210 if (ch == u'<') 0211 *d->out << QStringLiteral("<"); 0212 else if (ch == u'&') 0213 *d->out << QStringLiteral("&"); 0214 else 0215 *d->out << ch; 0216 } 0217 0218 if (!htmlStyle.isEmpty()) { 0219 *d->out << QStringLiteral("</span>"); 0220 } 0221 }