diff options
| author | Felix (xq) Queißner <git@mq32.de> | 2020-06-22 21:10:04 +0200 |
|---|---|---|
| committer | Felix (xq) Queißner <git@mq32.de> | 2020-06-22 21:10:04 +0200 |
| commit | 75ec461eeaa851cb5c53f4cfffc434e3e529ed1d (patch) | |
| tree | 3944737340718ca3675381aa06636045d397e780 /src/renderers/geminirenderer.cpp | |
| parent | 8dbfb0890560fd1cd698d06fa05ac868c4db8576 (diff) | |
| download | kristall-75ec461eeaa851cb5c53f4cfffc434e3e529ed1d.tar.gz | |
Restructures the project source and cleans up a bit
Diffstat (limited to 'src/renderers/geminirenderer.cpp')
| -rw-r--r-- | src/renderers/geminirenderer.cpp | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/src/renderers/geminirenderer.cpp b/src/renderers/geminirenderer.cpp new file mode 100644 index 0000000..ec00869 --- /dev/null +++ b/src/renderers/geminirenderer.cpp @@ -0,0 +1,343 @@ +#include "geminirenderer.hpp" + +#include <QTextList> +#include <QTextBlock> +#include <QList> +#include <QStringList> +#include <QDebug> + +#include "kristall.hpp" + +static QByteArray trim_whitespace(QByteArray items) +{ + int start = 0; + while (start < items.size() and isspace(items.at(start))) + { + start += 1; + } + int end = items.size() - 1; + while (end > 0 and isspace(items.at(end))) + { + end -= 1; + } + return items.mid(start, end - start + 1); +} + +std::unique_ptr<GeminiDocument> GeminiRenderer::render( + const QByteArray &input, + QUrl const &root_url, + DocumentStyle const & themed_style, + DocumentOutlineModel &outline) +{ + QTextCharFormat preformatted; + preformatted.setFont(themed_style.preformatted_font); + preformatted.setForeground(themed_style.preformatted_color); + + QTextCharFormat standard; + standard.setFont(themed_style.standard_font); + standard.setForeground(themed_style.standard_color); + + QTextCharFormat standard_link; + standard_link.setFont(themed_style.standard_font); + standard_link.setForeground(QBrush(themed_style.internal_link_color)); + + QTextCharFormat external_link; + external_link.setFont(themed_style.standard_font); + external_link.setForeground(QBrush(themed_style.external_link_color)); + + QTextCharFormat cross_protocol_link; + cross_protocol_link.setFont(themed_style.standard_font); + cross_protocol_link.setForeground(QBrush(themed_style.cross_scheme_link_color)); + + QTextCharFormat standard_h1; + standard_h1.setFont(themed_style.h1_font); + standard_h1.setForeground(QBrush(themed_style.h1_color)); + + QTextCharFormat standard_h2; + standard_h2.setFont(themed_style.h2_font); + standard_h2.setForeground(QBrush(themed_style.h2_color)); + + QTextCharFormat standard_h3; + standard_h3.setFont(themed_style.h3_font); + standard_h3.setForeground(QBrush(themed_style.h3_color)); + + std::unique_ptr<GeminiDocument> result = std::make_unique<GeminiDocument>(); + result->setDocumentMargin(themed_style.margin); + result->background_color = themed_style.background_color; + result->setIndentWidth(20); + + bool emit_fancy_text = global_options.enable_text_decoration; + + QTextCursor cursor{result.get()}; + + QTextBlockFormat standard_format = cursor.blockFormat(); + + QTextBlockFormat preformatted_format = standard_format; + preformatted_format.setNonBreakableLines(true); + + QTextBlockFormat block_quote_format = standard_format; + block_quote_format.setIndent(1); + block_quote_format.setBackground(themed_style.blockquote_color); + + + bool verbatim = false; + QTextList *current_list = nullptr; + bool blockquote = false; + + outline.beginBuild(); + + int anchor_id = 0; + + auto unique_anchor_name = [&]() -> QString { + return QString("auto-title-%1").arg(++anchor_id); + }; + + QList<QByteArray> lines = input.split('\n'); + for (auto const &line : lines) + { + if (verbatim) + { + if (line.startsWith("```")) + { + cursor.setBlockFormat(standard_format); + verbatim = false; + } + else + { + cursor.setBlockFormat(preformatted_format); + cursor.setCharFormat(preformatted); + cursor.insertText(line + "\n"); + } + } + else + { + if (line.startsWith("* ")) + { + if (current_list == nullptr) + { + cursor.deletePreviousChar(); + current_list = cursor.insertList(QTextListFormat::ListDisc); + } + else + { + cursor.insertBlock(); + } + + QString item = trim_whitespace(line.mid(1)); + + cursor.insertText(item, standard); + continue; + } + else + { + if (current_list != nullptr) + { + cursor.insertBlock(); + cursor.setBlockFormat(standard_format); + } + current_list = nullptr; + } + + if(line.startsWith(">")) + { + if(not blockquote ) { + // cursor.insertBlock(); + } + blockquote = true; + + cursor.setBlockFormat(block_quote_format); + cursor.insertText(trim_whitespace(line.mid(1)) + "\n", standard); + + continue; + } + else + { + if(blockquote) { + cursor.setBlockFormat(standard_format); + } + blockquote = false; + } + + if (line.startsWith("###")) + { + auto heading = trim_whitespace(line.mid(3)); + + auto id = unique_anchor_name(); + auto fmt = standard_h3; + fmt.setAnchor(true); + fmt.setAnchorNames(QStringList { id }); + + cursor.insertText(heading + "\n", fmt); + outline.appendH3(heading, id); + } + else if (line.startsWith("##")) + { + auto heading = trim_whitespace(line.mid(2)); + + auto id = unique_anchor_name(); + auto fmt = standard_h2; + fmt.setAnchor(true); + fmt.setAnchorNames(QStringList { id }); + + cursor.insertText(heading + "\n", fmt); + outline.appendH2(heading, id); + } + else if (line.startsWith("#")) + { + auto heading = trim_whitespace(line.mid(1)); + + auto id = unique_anchor_name(); + auto fmt = standard_h1; + fmt.setAnchor(true); + fmt.setAnchorNames(QStringList { id }); + + cursor.insertText(heading + "\n", fmt); + outline.appendH1(heading, id); + } + else if (line.startsWith("=>")) + { + auto const part = line.mid(2).trimmed(); + + QByteArray link, title; + + int index = -1; + for (int i = 0; i < part.size(); i++) + { + if (isspace(part[i])) + { + index = i; + break; + } + } + + if (index > 0) + { + link = trim_whitespace(part.mid(0, index)); + title = trim_whitespace(part.mid(index + 1)); + } + else + { + link = trim_whitespace(part); + title = trim_whitespace(part); + } + + auto local_url = QUrl(link); + + auto absolute_url = root_url.resolved(QUrl(link)); + + // qDebug() << link << title; + + auto fmt = standard_link; + + QString prefix; + if (absolute_url.host() == root_url.host()) + { + prefix = themed_style.internal_link_prefix; + fmt = standard_link; + } + else + { + prefix = themed_style.external_link_prefix; + fmt = external_link; + } + + QString suffix = ""; + if (absolute_url.scheme() != root_url.scheme()) + { + if(absolute_url.scheme() != "kristall+ctrl") { + suffix = " [" + absolute_url.scheme().toUpper() + "]"; + fmt = cross_protocol_link; + } + } + + fmt.setAnchor(true); + fmt.setAnchorHref(absolute_url.toString()); + cursor.insertText(prefix + title + suffix + "\n", fmt); + } + else if (line.startsWith("```")) + { + verbatim = true; + } + else + { + if(emit_fancy_text) + { + // TODO: Fix UTF-8 encoding here… Don't emit single characters but always spans! + + bool rendering_bold = false; + bool rendering_underlined = false; + + QTextCharFormat fmt = standard; + + QByteArray buffer; + + auto flush = [&]() { + if(buffer.size() > 0) { + cursor.insertText(QString::fromUtf8(buffer), fmt); + buffer.resize(0); + } + }; + + for(int i = 0; i < line.length(); i += 1) + { + char c = line.at(i); + if(c == ' ') { + flush(); + fmt = standard; + buffer.append(' '); + rendering_bold = false; + rendering_underlined = false; + } + else if(c == '*') { + if(rendering_bold) { + buffer.append('*'); + } + flush(); + rendering_bold = not rendering_bold; + auto f = fmt.font(); + f.setBold(rendering_bold); + fmt.setFont(f); + if(rendering_bold) { + buffer.append('*'); + } + } + else if(c == '_') { + if(rendering_underlined) { + buffer.append(' '); + } + flush(); + rendering_underlined = not rendering_underlined; + auto f = fmt.font(); + fmt.setUnderlineStyle(rendering_underlined ? QTextCharFormat::SingleUnderline : QTextCharFormat::NoUnderline); + if(rendering_underlined) { + buffer.append(' '); + } + } + else { + buffer.append(c); + } + } + + flush(); + + cursor.insertText("\n", standard); + } + else { + cursor.insertText(line + "\n", standard); + } + } + } + } + + outline.endBuild(); + return result; +} + +GeminiDocument::GeminiDocument(QObject *parent) : QTextDocument(parent), + background_color(0x00, 0x00, 0x00) +{ +} + +GeminiDocument::~GeminiDocument() +{ +} |
