|
|
|
@ -17,6 +17,7 @@ |
|
|
|
|
#include <QTextDocument> |
|
|
|
|
#include <QXmlStreamReader> |
|
|
|
|
|
|
|
|
|
#include <QTextBoundaryFinder> |
|
|
|
|
#include <cmath> |
|
|
|
|
#include <variant> |
|
|
|
|
|
|
|
|
@ -468,11 +469,92 @@ utils::escapeBlacklistedHtml(const QString &rawStr) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
QString |
|
|
|
|
utils::markdownToHtml(const QString &text) |
|
|
|
|
{ |
|
|
|
|
const auto str = text.toUtf8(); |
|
|
|
|
const char *tmp_buf = cmark_markdown_to_html(str.constData(), str.size(), CMARK_OPT_UNSAFE); |
|
|
|
|
utils::markdownToHtml(const QString &text, bool rainbowify) |
|
|
|
|
{ |
|
|
|
|
const auto str = text.toUtf8(); |
|
|
|
|
cmark_node *const node = |
|
|
|
|
cmark_parse_document(str.constData(), str.size(), CMARK_OPT_UNSAFE); |
|
|
|
|
|
|
|
|
|
if (rainbowify) { |
|
|
|
|
// create iterator over node
|
|
|
|
|
cmark_iter *iter = cmark_iter_new(node); |
|
|
|
|
|
|
|
|
|
cmark_event_type ev_type; |
|
|
|
|
|
|
|
|
|
// First loop to get total text length
|
|
|
|
|
int textLen = 0; |
|
|
|
|
while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { |
|
|
|
|
cmark_node *cur = cmark_iter_get_node(iter); |
|
|
|
|
// only text nodes (no code or semilar)
|
|
|
|
|
if (cmark_node_get_type(cur) != CMARK_NODE_TEXT) |
|
|
|
|
continue; |
|
|
|
|
// count up by length of current node's text
|
|
|
|
|
QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme, |
|
|
|
|
QString(cmark_node_get_literal(cur))); |
|
|
|
|
while (tbf.toNextBoundary() != -1) |
|
|
|
|
textLen++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// create new iter to start over
|
|
|
|
|
cmark_iter_free(iter); |
|
|
|
|
iter = cmark_iter_new(node); |
|
|
|
|
|
|
|
|
|
// Second loop to rainbowify
|
|
|
|
|
int charIdx = 0; |
|
|
|
|
while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { |
|
|
|
|
cmark_node *cur = cmark_iter_get_node(iter); |
|
|
|
|
// only text nodes (no code or semilar)
|
|
|
|
|
if (cmark_node_get_type(cur) != CMARK_NODE_TEXT) |
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
// get text in current node
|
|
|
|
|
QString nodeText(cmark_node_get_literal(cur)); |
|
|
|
|
// create buffer to append rainbow text to
|
|
|
|
|
QString buf; |
|
|
|
|
int boundaryStart = 0; |
|
|
|
|
int boundaryEnd = 0; |
|
|
|
|
// use QTextBoundaryFinder to iterate ofer graphemes
|
|
|
|
|
QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme, |
|
|
|
|
nodeText); |
|
|
|
|
while ((boundaryEnd = tbf.toNextBoundary()) != -1) { |
|
|
|
|
// Split text to get current char
|
|
|
|
|
auto curChar = |
|
|
|
|
nodeText.midRef(boundaryStart, boundaryEnd - boundaryStart); |
|
|
|
|
boundaryStart = boundaryEnd; |
|
|
|
|
// Don't rainbowify whitespaces
|
|
|
|
|
if (curChar.trimmed().isEmpty()) { |
|
|
|
|
buf.append(curChar.toString()); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// get correct color for char index
|
|
|
|
|
auto color = QColor::fromHsvF(1.0 / textLen * charIdx, 1.0, 1.0); |
|
|
|
|
// format color for HTML
|
|
|
|
|
auto colorString = color.name(QColor::NameFormat::HexRgb); |
|
|
|
|
// create HTML element for current char
|
|
|
|
|
auto curCharColored = QString("<font color=\"%0\">%1</font>") |
|
|
|
|
.arg(colorString) |
|
|
|
|
.arg(curChar); |
|
|
|
|
// append colored HTML element to buffer
|
|
|
|
|
buf.append(curCharColored); |
|
|
|
|
|
|
|
|
|
charIdx++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// create HTML_INLINE node to prevent HTML from being escaped
|
|
|
|
|
auto htmlNode = cmark_node_new(CMARK_NODE_HTML_INLINE); |
|
|
|
|
// set content of HTML node to buffer contents
|
|
|
|
|
cmark_node_set_literal(htmlNode, buf.toUtf8().data()); |
|
|
|
|
// replace current node with HTML node
|
|
|
|
|
cmark_node_replace(cur, htmlNode); |
|
|
|
|
// free memory of old node
|
|
|
|
|
cmark_node_free(cur); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cmark_iter_free(iter); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const char *tmp_buf = cmark_render_html(node, CMARK_OPT_UNSAFE); |
|
|
|
|
// Copy the null terminated output buffer.
|
|
|
|
|
std::string html(tmp_buf); |
|
|
|
|
|
|
|
|
|