kristall/src/geminirenderer.cpp

324 lines
10 KiB
C++

#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_settings.value("text_decoration").toBool();
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())
{
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;
for(int i = 0; i < line.length(); i += 1)
{
char c = line.at(i);
if(c == ' ') {
fmt = standard;
cursor.insertText(" ");
rendering_bold = false;
rendering_underlined = false;
}
else if(c == '*') {
if(rendering_bold)
cursor.insertText("*", fmt);
rendering_bold = not rendering_bold;
auto f = fmt.font();
f.setBold(rendering_bold);
fmt.setFont(f);
if(rendering_bold)
cursor.insertText("*", fmt);
}
else if(c == '_') {
if(rendering_underlined)
cursor.insertText(" ", fmt);
rendering_underlined = not rendering_underlined;
auto f = fmt.font();
fmt.setUnderlineStyle(rendering_underlined ? QTextCharFormat::SingleUnderline : QTextCharFormat::NoUnderline);
if(rendering_underlined)
cursor.insertText(" ", fmt);
}
else {
cursor.insertText(QString::fromUtf8(&c, 1), fmt);
}
}
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()
{
}