aboutsummaryrefslogtreecommitdiff
path: root/src/renderers/geminirenderer.cpp
diff options
context:
space:
mode:
authorMike Skec <skec@protonmail.ch>2021-03-07 10:50:55 +1100
committerFelix Queißner <felix@ib-queissner.de>2021-03-07 03:42:34 +0100
commiteca5fcc3b70e09052a2ad3087affac30954c0943 (patch)
tree9e20724d22c04df7766cf1c2de67fc352d7c2e42 /src/renderers/geminirenderer.cpp
parent7f0f312e77041484062d21419de796e991278ba6 (diff)
downloadkristall-eca5fcc3b70e09052a2ad3087affac30954c0943.tar.gz
GeminiRenderer: restructure code; makes highlighting work on non-paragraphs
Diffstat (limited to 'src/renderers/geminirenderer.cpp')
-rw-r--r--src/renderers/geminirenderer.cpp650
1 files changed, 328 insertions, 322 deletions
diff --git a/src/renderers/geminirenderer.cpp b/src/renderers/geminirenderer.cpp
index 0e62ef3..f81b12a 100644
--- a/src/renderers/geminirenderer.cpp
+++ b/src/renderers/geminirenderer.cpp
@@ -29,6 +29,7 @@ static QByteArray trim_whitespace(const QByteArray &items)
}
static QByteArray replace_quotes(QByteArray&);
+static void insertText(QTextCursor&, const QByteArray&, const QTextCharFormat&);
std::unique_ptr<GeminiDocument> GeminiRenderer::render(
const QByteArray &input,
@@ -43,8 +44,6 @@ std::unique_ptr<GeminiDocument> GeminiRenderer::render(
renderhelpers::setPageMargins(result.get(), themed_style.margin_h, themed_style.margin_v);
result->setIndentWidth(themed_style.indent_size);
- bool emit_fancy_text = kristall::globals().options.enable_text_decoration;
-
QTextCursor cursor{result.get()};
bool verbatim = false;
@@ -86,370 +85,216 @@ std::unique_ptr<GeminiDocument> GeminiRenderer::render(
cursor.setCharFormat(text_style.preformatted);
cursor.insertText(line + "\n");
}
+
+ continue;
}
- else
+
+ // List item
+ if (line.startsWith("* "))
{
- if (line.startsWith("* "))
+ if (current_list == nullptr)
{
- if (current_list == nullptr)
- {
- cursor.deletePreviousChar();
- cursor.insertBlock();
- cursor.setBlockFormat(text_style.standard_format);
- current_list = cursor.createList(text_style.list_format);
- }
- else
- {
- cursor.insertBlock();
- }
-
- replace_quotes(line);
- QString item = trim_whitespace(line.mid(1));
-
- cursor.insertText(item, text_style.standard);
- continue;
+ cursor.deletePreviousChar();
+ cursor.insertBlock();
+ cursor.setBlockFormat(text_style.standard_format);
+ current_list = cursor.createList(text_style.list_format);
}
else
{
- if (current_list != nullptr)
- {
- cursor.insertBlock();
- cursor.setBlockFormat(text_style.standard_format);
- }
- current_list = nullptr;
+ cursor.insertBlock();
}
- if(line.startsWith(">"))
- {
- if(!blockquote)
- {
- // Start blockquote
- QTextTable *table = cursor.insertTable(1, 1, text_style.blockquote_tableformat);
- cursor.setBlockFormat(text_style.blockquote_format);
- QTextTableCell cell = table->cellAt(0, 0);
- cell.setFormat(text_style.blockquote);
- blockquote = true;
- }
+ replace_quotes(line);
+ insertText(cursor, trim_whitespace(line.mid(1)), text_style.standard);
+ continue;
+ }
- replace_quotes(line);
- cursor.insertText(trim_whitespace(line.mid(1)) + "\n", text_style.blockquote);
+ // End of list
+ if (current_list != nullptr)
+ {
+ cursor.insertBlock();
+ cursor.setBlockFormat(text_style.standard_format);
+ }
+ current_list = nullptr;
- continue;
- }
- else
+ // Block quote
+ if(line.startsWith(">"))
+ {
+ if(!blockquote)
{
- if (blockquote)
- {
- // End blockquote
- cursor.deletePreviousChar();
- cursor.movePosition(QTextCursor::NextBlock);
- cursor.setBlockFormat(text_style.standard_format);
- }
- blockquote = false;
+ // Start blockquote
+ QTextTable *table = cursor.insertTable(1, 1, text_style.blockquote_tableformat);
+ cursor.setBlockFormat(text_style.blockquote_format);
+ QTextTableCell cell = table->cellAt(0, 0);
+ cell.setFormat(text_style.blockquote);
+ blockquote = true;
}
- if (line.startsWith("###"))
- {
- auto heading = trim_whitespace(line.mid(3));
+ replace_quotes(line);
+ insertText(cursor, trim_whitespace(line.mid(1)), text_style.blockquote);
+ cursor.insertText("\n", text_style.standard);
+ continue;
+ }
- auto id = unique_anchor_name();
- auto fmt = text_style.standard_h3;
- fmt.setAnchor(true);
- fmt.setAnchorNames(QStringList { id });
+ // End of blockquote
+ if (blockquote)
+ {
+ cursor.deletePreviousChar();
+ cursor.movePosition(QTextCursor::NextBlock);
+ cursor.setBlockFormat(text_style.standard_format);
+ }
+ blockquote = false;
- outline.appendH3(heading, id);
+ // Headings, etc.
+ if (line.startsWith("###"))
+ {
+ auto heading = trim_whitespace(line.mid(3));
- cursor.setBlockFormat(text_style.heading_format);
- cursor.insertText(replace_quotes(heading), fmt);
- cursor.insertText("\n", text_style.standard);
- }
- else if (line.startsWith("##"))
- {
- auto heading = trim_whitespace(line.mid(2));
+ auto id = unique_anchor_name();
+ auto fmt = text_style.standard_h3;
+ fmt.setAnchor(true);
+ fmt.setAnchorNames(QStringList { id });
- auto id = unique_anchor_name();
- auto fmt = text_style.standard_h2;
- fmt.setAnchor(true);
- fmt.setAnchorNames(QStringList { id });
+ outline.appendH3(heading, id);
- outline.appendH2(heading, id);
+ cursor.setBlockFormat(text_style.heading_format);
+ insertText(cursor, replace_quotes(heading), fmt);
+ cursor.insertText("\n", text_style.standard);
+ }
+ else if (line.startsWith("##"))
+ {
+ auto heading = trim_whitespace(line.mid(2));
- cursor.setBlockFormat(text_style.heading_format);
- cursor.insertText(replace_quotes(heading), fmt);
- cursor.insertText("\n", text_style.standard);
- }
- else if (line.startsWith("#"))
- {
- auto heading = trim_whitespace(line.mid(1));
+ auto id = unique_anchor_name();
+ auto fmt = text_style.standard_h2;
+ fmt.setAnchor(true);
+ fmt.setAnchorNames(QStringList { id });
- auto id = unique_anchor_name();
- auto fmt = text_style.standard_h1;
- fmt.setAnchor(true);
- fmt.setAnchorNames(QStringList { id });
+ outline.appendH2(heading, id);
- outline.appendH1(heading, id);
+ cursor.setBlockFormat(text_style.heading_format);
+ insertText(cursor, replace_quotes(heading), fmt);
+ cursor.insertText("\n", text_style.standard);
+ }
+ else if (line.startsWith("#"))
+ {
+ auto heading = trim_whitespace(line.mid(1));
- // Use first heading as the page's title.
- if (page_title != nullptr && page_title->isEmpty())
- {
- *page_title = heading;
- }
+ auto id = unique_anchor_name();
+ auto fmt = text_style.standard_h1;
+ fmt.setAnchor(true);
+ fmt.setAnchorNames(QStringList { id });
- // Centre the first heading. We can't use the above code block
- // for this because it doesn't get run on every re-render of the page
- if (centre_first_h1)
- {
- auto f = text_style.heading_format;
- f.setAlignment(Qt::AlignCenter);
- cursor.setBlockFormat(f);
- centre_first_h1 = false;
- }
- else
- {
- cursor.setBlockFormat(text_style.heading_format);
- }
+ outline.appendH1(heading, id);
- cursor.insertText(replace_quotes(heading), fmt);
- cursor.insertText("\n", text_style.standard);
+ // Use first heading as the page's title.
+ if (page_title != nullptr && page_title->isEmpty())
+ {
+ *page_title = heading;
+ }
+
+ // Centre the first heading. We can't use the above code block
+ // for this because it doesn't get run on every re-render of the page
+ if (centre_first_h1)
+ {
+ auto f = text_style.heading_format;
+ f.setAlignment(Qt::AlignCenter);
+ cursor.setBlockFormat(f);
+ centre_first_h1 = false;
}
- else if (line.startsWith("=>"))
+ else
{
- auto const part = line.mid(2).trimmed();
+ cursor.setBlockFormat(text_style.heading_format);
+ }
- QByteArray link, title;
+ insertText(cursor, replace_quotes(heading), fmt);
+ cursor.insertText("\n", text_style.standard);
+ }
+ else if (line.startsWith("=>"))
+ {
+ auto const part = line.mid(2).trimmed();
- int index = -1;
- for (int i = 0; i < part.size(); i++)
- {
- if (isspace(part[i]))
- {
- index = i;
- break;
- }
- }
+ QByteArray link, title;
- if (index > 0)
- {
- link = trim_whitespace(part.mid(0, index));
- title = trim_whitespace(part.mid(index + 1));
- }
- else
+ int index = -1;
+ for (int i = 0; i < part.size(); i++)
+ {
+ if (isspace(part[i]))
{
- link = trim_whitespace(part);
- title = trim_whitespace(part);
+ index = i;
+ break;
}
- replace_quotes(title);
-
- auto local_url = QUrl(link);
+ }
- // Makes relative URLs with scheme provided (e.g gemini:///relative) work
- // From RFC 1630: "If the scheme parts are different, the whole absolute URI must be given"
- // therefor the schemes must be same for this to be allowed.
- if (local_url.scheme() == root_url.scheme() &&
- local_url.authority().isEmpty() &&
- local_url.scheme() != "about" &&
- local_url.scheme() != "file")
- {
- // qDebug() << "Adjusting local url: " << local_url;
- local_url = local_url.adjusted(QUrl::RemoveScheme | QUrl::RemoveAuthority);
- }
- auto absolute_url = root_url.resolved(local_url);
+ 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);
+ }
+ replace_quotes(title);
- // qDebug() << link << title;
+ auto local_url = QUrl(link);
- auto fmt = text_style.standard_link;
+ // Makes relative URLs with scheme provided (e.g gemini:///relative) work
+ // From RFC 1630: "If the scheme parts are different, the whole absolute URI must be given"
+ // therefor the schemes must be same for this to be allowed.
+ if (local_url.scheme() == root_url.scheme() &&
+ local_url.authority().isEmpty() &&
+ local_url.scheme() != "about" &&
+ local_url.scheme() != "file")
+ {
+ // qDebug() << "Adjusting local url: " << local_url;
+ local_url = local_url.adjusted(QUrl::RemoveScheme | QUrl::RemoveAuthority);
+ }
+ auto absolute_url = root_url.resolved(local_url);
- QString prefix;
- if (absolute_url.host() == root_url.host())
- {
- prefix = themed_style.internal_link_prefix;
- fmt = text_style.standard_link;
- }
- else
- {
- prefix = themed_style.external_link_prefix;
- fmt = text_style.external_link;
- }
+ // qDebug() << link << title;
- QString suffix = "";
- if (absolute_url.scheme() != root_url.scheme())
- {
- if(absolute_url.scheme() != "kristall+ctrl") {
- suffix = " [" + absolute_url.scheme().toUpper() + "]";
- fmt = text_style.cross_protocol_link;
- }
- }
+ auto fmt = text_style.standard_link;
- fmt.setAnchor(true);
- fmt.setAnchorHref(absolute_url.toString());
- cursor.setBlockFormat(text_style.link_format);
- cursor.insertText(prefix + title + suffix + "\n", fmt);
- }
- else if (line.startsWith("```"))
+ QString prefix;
+ if (absolute_url.host() == root_url.host())
{
- verbatim = true;
+ prefix = themed_style.internal_link_prefix;
+ fmt = text_style.standard_link;
}
else
{
- cursor.setBlockFormat(text_style.standard_format);
- replace_quotes(line);
+ prefix = themed_style.external_link_prefix;
+ fmt = text_style.external_link;
+ }
- if(emit_fancy_text)
- {
- // Just render lines not containing asterisks/underscores normally.
- // This actually helps reduce the small overhead on large pages to
- // being almost negligable
- if (!line.contains("*") && !line.contains("_"))
- {
- cursor.insertText(line + "\n", text_style.standard);
- continue;
- }
-
- // Easier to work on this as an array of QChars
- QString text(line);
-
- // Whether to hide formatting codes (*, and _). This option
- // is mainly here so that the code which strips these is
- // more understandable.
- static const bool HIDE_FORMATS = true;
-
- // The first thing we do is convert double-asterisk bolding to single-asterisk.
- // This makes it A LOT easier to bold these things.
- //
- // This is done using this regex. In a simpler, pseudo form, it can be written as:
- // (punctuation/whitespace/line-begin)+\*\*(bolded text)\*\*(punctuation/whitespace/EOL)
- // Just stare at it a bit and you might figure out how it works...
- QRegularExpression BOLD_DBL_REGEX
- = QRegularExpression(R"((^|[\s.,!?[\]()\\-])+\*\*([^\*\s]+[^\*]+[^\*\s]+)\*\*($|[\s.,!?[\]()\\-]))");
- text.replace(BOLD_DBL_REGEX, QString(R"(\1*\2*\3)"));
-
- QTextCharFormat fmt = text_style.standard;
- bool bold = false, underline = false;
- bool was_bold = false, was_underline = false;
- int last = 0;
-
- // Used to prepare the format before actually drawing the text.
- auto format_text = [&bold, &underline, &was_bold, &was_underline, &last, &text, &fmt](int i) -> QString
- {
- // Makes sure that bold/underline text only gets printed
- // if it has a matching * or _.
- if (bold && !text.mid(i, text.length() - i).contains("*"))
- bold = false;
- if (underline && !text.mid(i, text.length() - i).contains("_"))
- underline = false;
-
- // Sets format to bold/underline as necessary.
- auto f = fmt.font();
- f.setBold(bold);
- fmt.setFont(f);
- fmt.setUnderlineStyle(underline ?
- QTextCharFormat::SingleUnderline : QTextCharFormat::NoUnderline);
-
- // Remove formats
- QString span = text.mid(last, i - last);
- if (HIDE_FORMATS &&
- span.length() > 1 &&
- (((bold || was_bold) && span.startsWith("*")) ||
- ((underline || was_underline) && span.startsWith("_"))))
- {
- span = span.mid(1, span.length() - 1);
- }
-
- return span;
- };
-
- for (int i = 0; i < text.length(); ++i)
- {
- if (text[i] == '*')
- {
- // Format and insert the text.
- cursor.insertText(format_text(i), fmt);
-
- // 'Toggle' bold state.
- if (was_bold) was_bold = false;
- if (bold) {
- was_bold = true;
- bold = false;
- } else {
- // Only start bold formatting if this looks like bold formatting:
- // * Previous char must be either whitespace, nothing
- // * Next char must not be: whitespace, comma, full-stop, asterisk, or underscore.
- if ((i == 0 || text[i - 1].isSpace()) &&
- (i + 1) < text.length() &&
- !text[i + 1].isSpace() &&
- text[i + 1] != ',' &&
- text[i + 1] != '.' &&
- text[i + 1] != '*' &&
- text[i + 1] != '_')
- {
- bold = true;
- }
- }
-
- last = i;
- }
- else if (text[i] == '_')
- {
- // Insert the text
- cursor.insertText(format_text(i), fmt);
-
- // 'Toggle' underline state.
- if (was_underline) was_underline = false;
- if (underline) {
- was_underline = true;
- underline = false;
- } else {
- // Only start underline formatting if it looks like an underline.
- // * Previous char must be either whitespace or nothing
- // * Next char must not be: whitespace, comma, full-stop, asterisk, or underscore.
- if ((i == 0 || text[i - 1].isSpace()) &&
- (i + 1) < text.length() &&
- !text[i + 1].isSpace() &&
- text[i + 1] != ',' &&
- text[i + 1] != '.' &&
- text[i + 1] != '*' &&
- text[i + 1] != '_')
- {
- underline = true;
- }
- }
-
- last = i;
- }
-
- if (i == text.length() - 1)
- {
- QString span = text.mid(last, i - last + 1);
-
- // Skip if the span is just an asterisk/underline
- if (HIDE_FORMATS &&
- ((was_bold && span == "*") ||
- (was_underline && span == "_")))
- {
- break;
- }
-
- // Strips previous underline/asterisk
- if (HIDE_FORMATS &&
- span.length() > 1 &&
- ((was_bold && span.startsWith("*")) ||
- (was_underline && span.startsWith("_"))))
- {
- span = span.mid(1, span.length() - 1);
- }
-
- // Draw ending text normally.
- cursor.insertText(span, text_style.standard);
- break;
- }
- }
-
- cursor.insertText("\n", text_style.standard);
- }
- else {
- cursor.insertText(line + "\n", text_style.standard);
+ QString suffix = "";
+ if (absolute_url.scheme() != root_url.scheme())
+ {
+ if(absolute_url.scheme() != "kristall+ctrl") {
+ suffix = " [" + absolute_url.scheme().toUpper() + "]";
+ fmt = text_style.cross_protocol_link;
}
}
+
+ fmt.setAnchor(true);
+ fmt.setAnchorHref(absolute_url.toString());
+ cursor.setBlockFormat(text_style.link_format);
+ insertText(cursor, (prefix + title + suffix).toUtf8(), fmt);
+ cursor.insertText("\n", text_style.standard);
+ }
+ else if (line.startsWith("```"))
+ {
+ verbatim = true;
+ }
+ else
+ {
+ cursor.setBlockFormat(text_style.standard_format);
+
+ replace_quotes(line);
+ insertText(cursor, line, text_style.standard);
+ cursor.insertText("\n", text_style.standard);
}
}
@@ -537,3 +382,164 @@ static QByteArray replace_quotes(QByteArray &line)
return line;
}
+
+/*
+ * Handles all the fancy text highlighting.
+ */
+static void insertText(QTextCursor &cursor, const QByteArray &line,
+ const QTextCharFormat &format)
+{
+ if (line.isEmpty() ||
+
+ // Render text normally if text decoration is disabled.
+ !kristall::globals().options.enable_text_decoration ||
+
+ // Render lines not containing asterisks/underscores normally.
+ // This actually helps reduce the small overhead on large pages to
+ // being almost negligable
+ (!line.contains("*") && !line.contains("_")))
+ {
+ // Empty lines must be in standard format.
+ cursor.insertText(line, format);
+ return;
+ }
+
+ // Easier to work on this as an array of QChars
+ QString text(line);
+
+ // Whether to hide formatting codes (*, and _). This option
+ // is mainly here so that the code which strips these is
+ // more understandable.
+ static const bool HIDE_FORMATS = true;
+
+ // The first thing we do is convert double-asterisk bolding to single-asterisk.
+ // This makes it A LOT easier to bold these things.
+ //
+ // This is done using this regex. In a simpler, pseudo form, it can be written as:
+ // (punctuation/whitespace/line-begin)+\*\*(bolded text)\*\*(punctuation/whitespace/EOL)
+ // Just stare at it a bit and you might figure out how it works...
+ QRegularExpression BOLD_DBL_REGEX
+ = QRegularExpression(R"((^|[\s.,!?[\]()\\-])+\*\*([^\*\s]+[^\*]+[^\*\s]+)\*\*($|[\s.,!?[\]()\\-]))");
+ text.replace(BOLD_DBL_REGEX, QString(R"(\1*\2*\3)"));
+
+ QTextCharFormat fmt = format;
+ bool bold = false, underline = false;
+ bool was_bold = false, was_underline = false;
+ int last = 0;
+
+ // Used to prepare the format before actually drawing the text.
+ auto format_text = [&bold, &underline, &was_bold, &was_underline, &last, &text, &fmt](int i) -> QString
+ {
+ // Makes sure that bold/underline text only gets printed
+ // if it has a matching * or _.
+ if (bold && !text.mid(i, text.length() - i).contains("*"))
+ bold = false;
+ if (underline && !text.mid(i, text.length() - i).contains("_"))
+ underline = false;
+
+ // Sets format to bold/underline as necessary.
+ auto f = fmt.font();
+ f.setBold(bold);
+ fmt.setFont(f);
+ fmt.setUnderlineStyle(underline ?
+ QTextCharFormat::SingleUnderline : QTextCharFormat::NoUnderline);
+
+ // Remove formats
+ QString span = text.mid(last, i - last);
+ if (HIDE_FORMATS &&
+ span.length() > 1 &&
+ (((bold || was_bold) && span.startsWith("*")) ||
+ ((underline || was_underline) && span.startsWith("_"))))
+ {
+ span = span.mid(1, span.length() - 1);
+ }
+
+ return span;
+ };
+
+ for (int i = 0; i < text.length(); ++i)
+ {
+ if (text[i] == '*')
+ {
+ // Format and insert the text.
+ cursor.insertText(format_text(i), fmt);
+
+ // 'Toggle' bold state.
+ if (was_bold) was_bold = false;
+ if (bold) {
+ was_bold = true;
+ bold = false;
+ } else {
+ // Only start bold formatting if this looks like bold formatting:
+ // * Previous char must be either whitespace, nothing
+ // * Next char must not be: whitespace, comma, full-stop, asterisk, or underscore.
+ if ((i == 0 || text[i - 1].isSpace()) &&
+ (i + 1) < text.length() &&
+ !text[i + 1].isSpace() &&
+ text[i + 1] != ',' &&
+ text[i + 1] != '.' &&
+ text[i + 1] != '*' &&
+ text[i + 1] != '_')
+ {
+ bold = true;
+ }
+ }
+
+ last = i;
+ }
+ else if (text[i] == '_')
+ {
+ // Insert the text
+ cursor.insertText(format_text(i), fmt);
+
+ // 'Toggle' underline state.
+ if (was_underline) was_underline = false;
+ if (underline) {
+ was_underline = true;
+ underline = false;
+ } else {
+ // Only start underline formatting if it looks like an underline.
+ // * Previous char must be either whitespace or nothing
+ // * Next char must not be: whitespace, comma, full-stop, asterisk, or underscore.
+ if ((i == 0 || text[i - 1].isSpace()) &&
+ (i + 1) < text.length() &&
+ !text[i + 1].isSpace() &&
+ text[i + 1] != ',' &&
+ text[i + 1] != '.' &&
+ text[i + 1] != '*' &&
+ text[i + 1] != '_')
+ {
+ underline = true;
+ }
+ }
+
+ last = i;
+ }
+
+ if (i == text.length() - 1)
+ {
+ QString span = text.mid(last, i - last + 1);
+
+ // Skip if the span is just an asterisk/underline
+ if (HIDE_FORMATS &&
+ ((was_bold && span == "*") ||
+ (was_underline && span == "_")))
+ {
+ break;
+ }
+
+ // Strips previous underline/asterisk
+ if (HIDE_FORMATS &&
+ span.length() > 1 &&
+ ((was_bold && span.startsWith("*")) ||
+ (was_underline && span.startsWith("_"))))
+ {
+ span = span.mid(1, span.length() - 1);
+ }
+
+ // Draw ending text normally.
+ cursor.insertText(span, format);
+ break;
+ }
+ }
+}