Public Access
GeminiService fetches on a short-lived task and hands the App a GeminiPage: header, the body as lines in 4 KB chunks (TextBuffer: no large block, no doubling copies), the final URL after up to 5 redirects. Certificates are pinned on first use per host and port; a change comes back as its own outcome with both fingerprints. No fetch starts below 55 KB free (Q86). With a card, the body streams to /gemini/cache/page.gmi in 1 KB pieces while the connection is open, then loads into RAM once its memory is back (Q87); StorageService::runAndWait (moved from the Debug Console) keeps every card access on the storage task. Without a card: RAM, with the steady and transient floors. Measured with IRC connected: Cosmos (31.6 KB) went from 4.6 KB to the whole page on the card and 20 KB on screen; lowest free heap 19.5 KB in transfer, 43 KB once loaded. `gemini get` and `gemini trust` on the console. 14 Gemini tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
101 lines
3.3 KiB
C++
101 lines
3.3 KiB
C++
#include "gemtext.h"
|
|
|
|
#include <cstdint>
|
|
|
|
namespace roro::gemini {
|
|
|
|
namespace {
|
|
std::string trim(const std::string& s) {
|
|
size_t a = s.find_first_not_of(" \t"), b = s.find_last_not_of(" \t");
|
|
return a == std::string::npos ? "" : s.substr(a, b - a + 1);
|
|
}
|
|
} // namespace
|
|
|
|
GemLine parseGemLine(const std::string& line, bool& pre) {
|
|
GemLine g;
|
|
if (line.compare(0, 3, "```") == 0) {
|
|
g.type = LineType::PreToggle;
|
|
g.text = trim(line.substr(3));
|
|
pre = !pre;
|
|
} else if (pre) {
|
|
g.type = LineType::Preformatted;
|
|
g.text = line;
|
|
} else if (line.compare(0, 2, "=>") == 0) {
|
|
g.type = LineType::Link;
|
|
std::string rest = trim(line.substr(2));
|
|
size_t gap = rest.find_first_of(" \t");
|
|
g.url = rest.substr(0, gap);
|
|
g.text = gap == std::string::npos ? "" : trim(rest.substr(gap));
|
|
if (g.text.empty()) g.text = g.url;
|
|
} else if (line.compare(0, 3, "###") == 0) {
|
|
g.type = LineType::Heading3;
|
|
g.text = trim(line.substr(3));
|
|
} else if (line.compare(0, 2, "##") == 0) {
|
|
g.type = LineType::Heading2;
|
|
g.text = trim(line.substr(2));
|
|
} else if (line.compare(0, 1, "#") == 0) {
|
|
g.type = LineType::Heading1;
|
|
g.text = trim(line.substr(1));
|
|
} else if (line.compare(0, 2, "* ") == 0) {
|
|
g.type = LineType::ListItem;
|
|
g.text = trim(line.substr(2));
|
|
} else if (line.compare(0, 1, ">") == 0) {
|
|
g.type = LineType::Quote;
|
|
g.text = trim(line.substr(1));
|
|
} else {
|
|
g.text = line;
|
|
}
|
|
return g;
|
|
}
|
|
|
|
std::vector<GemLine> parseGemtext(const std::string& document) {
|
|
std::vector<GemLine> lines;
|
|
bool pre = false;
|
|
size_t start = 0;
|
|
while (start < document.size()) {
|
|
size_t end = document.find('\n', start);
|
|
if (end == std::string::npos) end = document.size();
|
|
std::string line = document.substr(start, end - start);
|
|
if (!line.empty() && line.back() == '\r') line.pop_back();
|
|
start = end + 1;
|
|
lines.push_back(parseGemLine(line, pre));
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
std::string displayText(const std::string& s) {
|
|
std::string out;
|
|
size_t column = 0;
|
|
for (size_t i = 0; i < s.size();) {
|
|
unsigned char c = static_cast<unsigned char>(s[i]);
|
|
if (c == '\t') {
|
|
do out += ' ';
|
|
while (++column % 4);
|
|
i++;
|
|
continue;
|
|
}
|
|
if (c < 0x80) {
|
|
out += static_cast<char>(c);
|
|
i++;
|
|
} else {
|
|
int len = (c & 0xE0) == 0xC0 ? 2 : (c & 0xF0) == 0xE0 ? 3 : (c & 0xF8) == 0xF0 ? 4 : 0;
|
|
bool valid = len > 0 && i + len <= s.size();
|
|
for (int k = 1; valid && k < len; k++) valid = (static_cast<unsigned char>(s[i + k]) & 0xC0) == 0x80;
|
|
if (!valid) {
|
|
out += '?';
|
|
i++;
|
|
} else {
|
|
uint32_t cp = len == 2 ? (c & 0x1F) : len == 3 ? (c & 0x0F) : (c & 0x07);
|
|
for (int k = 1; k < len; k++) cp = (cp << 6) | (static_cast<unsigned char>(s[i + k]) & 0x3F);
|
|
if (cp <= 0xFF) out.append(s, i, len); // Latin-1: the fonts have it
|
|
else out += '?';
|
|
i += len;
|
|
}
|
|
}
|
|
column++;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
} // namespace roro::gemini
|