KSyntaxHighlighting

htmlhighlighter.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
3 SPDX-FileCopyrightText: 2018 Christoph Cullmann <cullmann@kde.org>
4
5 SPDX-License-Identifier: MIT
6*/
7
8#include "htmlhighlighter.h"
9#include "abstracthighlighter_p.h"
10#include "definition.h"
11#include "definition_p.h"
12#include "format.h"
13#include "ksyntaxhighlighting_logging.h"
14#include "state.h"
15#include "theme.h"
16
17#include <QFile>
18#include <QFileInfo>
19#include <QIODevice>
20#include <QTextStream>
21
22using namespace KSyntaxHighlighting;
23
24class KSyntaxHighlighting::HtmlHighlighterPrivate : public AbstractHighlighterPrivate
25{
26public:
27 std::unique_ptr<QTextStream> out;
28 std::unique_ptr<QFile> file;
29 QString currentLine;
30 std::vector<QString> htmlStyles;
32};
33
34HtmlHighlighter::HtmlHighlighter()
35 : AbstractHighlighter(new HtmlHighlighterPrivate())
36{
37}
38
39HtmlHighlighter::~HtmlHighlighter()
40{
41}
42
43void HtmlHighlighter::setBackgroundRole(Theme::EditorColorRole bgRole)
44{
45 Q_D(HtmlHighlighter);
46 d->bgRole = bgRole;
47}
48
49void HtmlHighlighter::setOutputFile(const QString &fileName)
50{
51 Q_D(HtmlHighlighter);
52 d->file.reset(new QFile(fileName));
53 if (!d->file->open(QFile::WriteOnly | QFile::Truncate)) {
54 qCWarning(Log) << "Failed to open output file" << fileName << ":" << d->file->errorString();
55 return;
56 }
57 d->out.reset(new QTextStream(d->file.get()));
58 d->out->setEncoding(QStringConverter::Utf8);
59}
60
61void HtmlHighlighter::setOutputFile(FILE *fileHandle)
62{
63 Q_D(HtmlHighlighter);
64 d->out.reset(new QTextStream(fileHandle, QIODevice::WriteOnly));
65 d->out->setEncoding(QStringConverter::Utf8);
66}
67
68void HtmlHighlighter::highlightFile(const QString &fileName, const QString &title)
69{
70 QFile f(fileName);
71 if (!f.open(QFile::ReadOnly)) {
72 qCWarning(Log) << "Failed to open input file" << fileName << ":" << f.errorString();
73 return;
74 }
75
76 if (title.isEmpty()) {
77 QFileInfo fi(fileName);
78 highlightData(&f, fi.fileName());
79 } else {
80 highlightData(&f, title);
81 }
82}
83
84namespace
85{
86/**
87 * @brief toHtmlRgba
88 * Converts QRgb -> #RRGGBBAA if there is an alpha channel
89 * otherwise it will just return the hexcode. This is because QColor
90 * outputs #AARRGGBB, whereas browser support #RRGGBBAA.
91 */
92struct HtmlColor {
93 HtmlColor(QRgb argb)
94 {
95 static const char16_t *digits = u"0123456789abcdef";
96
97 hexcode[0] = u'#';
98 hexcode[1] = digits[qRed(argb) >> 4];
99 hexcode[2] = digits[qRed(argb) & 0xf];
100 hexcode[3] = digits[qGreen(argb) >> 4];
101 hexcode[4] = digits[qGreen(argb) & 0xf];
102 hexcode[5] = digits[qBlue(argb) >> 4];
103 hexcode[6] = digits[qBlue(argb) & 0xf];
104 if (qAlpha(argb) == 0xff) {
105 len = 7;
106 } else {
107 hexcode[7] = digits[qAlpha(argb) >> 4];
108 hexcode[8] = digits[qAlpha(argb) & 0xf];
109 len = 9;
110 }
111 }
112
113 QStringView sv() const
114 {
115 return QStringView(hexcode, len);
116 }
117
118private:
119 QChar hexcode[9];
120 qsizetype len;
121};
122}
123
124void HtmlHighlighter::highlightData(QIODevice *dev, const QString &title)
125{
126 Q_D(HtmlHighlighter);
127
128 if (!d->out) {
129 qCWarning(Log) << "No output stream defined!";
130 return;
131 }
132
133 QString htmlTitle;
134 if (title.isEmpty()) {
135 htmlTitle = QStringLiteral("KSyntaxHighlighter");
136 } else {
137 htmlTitle = title.toHtmlEscaped();
138 }
139
140 const auto &theme = d->m_theme;
141 const auto &definition = d->m_definition;
142 const bool useSelectedText = d->bgRole == Theme::TextSelection;
143
144 auto definitions = definition.includedDefinitions();
145 definitions.append(definition);
146
147 const auto mainTextColor = [&] {
148 if (useSelectedText) {
149 const auto fg = theme.selectedTextColor(Theme::Normal);
150 if (fg) {
151 return fg;
152 }
153 }
155 }();
156 const auto mainBgColor = theme.editorColor(d->bgRole);
157
158 int maxId = 0;
159 for (const auto &definition : std::as_const(definitions)) {
160 for (const auto &format : std::as_const(DefinitionData::get(definition)->formats)) {
161 maxId = qMax(maxId, format.id());
162 }
163 }
164 d->htmlStyles.clear();
165 // htmlStyles must not be empty for applyFormat to work even with a definition without any context
166 d->htmlStyles.resize(maxId + 1);
167
168 // initialize htmlStyles
169 for (const auto &definition : std::as_const(definitions)) {
170 for (const auto &format : std::as_const(DefinitionData::get(definition)->formats)) {
171 auto &buffer = d->htmlStyles[format.id()];
172
173 const auto textColor = useSelectedText ? format.selectedTextColor(theme).rgba() : format.textColor(theme).rgba();
174 if (textColor && textColor != mainTextColor) {
175 buffer += QStringLiteral("color:") + HtmlColor(textColor).sv() + u';';
176 }
177 const auto bgColor = useSelectedText ? format.selectedBackgroundColor(theme).rgba() : format.backgroundColor(theme).rgba();
178 if (bgColor && bgColor != mainBgColor) {
179 buffer += QStringLiteral("background-color:") + HtmlColor(bgColor).sv() + u';';
180 }
181 if (format.isBold(theme)) {
182 buffer += QStringLiteral("font-weight:bold;");
183 }
184 if (format.isItalic(theme)) {
185 buffer += QStringLiteral("font-style:italic;");
186 }
187 if (format.isUnderline(theme)) {
188 buffer += QStringLiteral("text-decoration:underline;");
189 }
190 if (format.isStrikeThrough(theme)) {
191 buffer += QStringLiteral("text-decoration:line-through;");
192 }
193
194 if (!buffer.isEmpty()) {
195 buffer.insert(0, QStringLiteral("<span style=\""));
196 // replace last ';'
197 buffer.back() = u'"';
198 buffer += u'>';
199 }
200 }
201 }
202
203 State state;
204 *d->out << "<!DOCTYPE html>\n";
205 *d->out << "<html><head>\n";
206 *d->out << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n";
207 *d->out << "<title>" << htmlTitle << "</title>\n";
208 *d->out << "<meta name=\"generator\" content=\"KF5::SyntaxHighlighting - Definition (" << definition.name() << ") - Theme (" << theme.name() << ")\"/>\n";
209 *d->out << "</head><body";
210 *d->out << " style=\"background-color:" << HtmlColor(mainBgColor).sv();
211 *d->out << ";color:" << HtmlColor(mainTextColor).sv();
212 *d->out << "\"><pre>\n";
213
214 QTextStream in(dev);
215 while (in.readLineInto(&d->currentLine)) {
216 state = highlightLine(d->currentLine, state);
217 *d->out << "\n";
218 }
219
220 *d->out << "</pre></body></html>\n";
221 d->out->flush();
222
223 d->out.reset();
224 d->file.reset();
225}
226
227void HtmlHighlighter::applyFormat(int offset, int length, const Format &format)
228{
229 if (length == 0) {
230 return;
231 }
232
233 Q_D(HtmlHighlighter);
234
235 auto const &htmlStyle = d->htmlStyles[format.id()];
236
237 if (!htmlStyle.isEmpty()) {
238 *d->out << htmlStyle;
239 }
240
241 for (QChar ch : QStringView(d->currentLine).sliced(offset, length)) {
242 if (ch == u'<')
243 *d->out << QStringLiteral("&lt;");
244 else if (ch == u'&')
245 *d->out << QStringLiteral("&amp;");
246 else
247 *d->out << ch;
248 }
249
250 if (!htmlStyle.isEmpty()) {
251 *d->out << QStringLiteral("</span>");
252 }
253}
Abstract base class for highlighters.
Theme theme() const
Returns the currently selected theme for highlighting.
Definition definition() const
Returns the syntax definition used for highlighting.
State highlightLine(QStringView text, const State &state)
Highlight the given line.
QList< Definition > includedDefinitions() const
Returns a list of Definitions that are referenced with the IncludeRules rule.
Describes the format to be used for a specific text fragment.
Definition format.h:28
int id() const
Returns a unique identifier of this format.
Definition format.cpp:100
QRgb textColor(TextStyle style) const
Returns the text color to be used for style.
Definition theme.cpp:62
QRgb selectedTextColor(TextStyle style) const
Returns the selected text color to be used for style.
Definition theme.cpp:67
EditorColorRole
Editor color roles, used to paint line numbers, editor background etc.
Definition theme.h:158
@ BackgroundColor
Background color for the editing area.
Definition theme.h:160
@ TextSelection
Background color for selected text.
Definition theme.h:162
@ Normal
Default text style for normal text and source code without special highlighting.
Definition theme.h:78
QRgb editorColor(EditorColorRole role) const
Returns the editor color for the requested role.
Definition theme.cpp:102
Syntax highlighting engine for Kate syntax definitions.
bool isEmpty() const const
QString toHtmlEscaped() const const
QStringView sliced(qsizetype pos) const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:09:06 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.