Files
pyxis/lib/tdeck_ui/UI/LXMF/NomadNetCompactPage.cpp
T

194 lines
8.1 KiB
C++

#include "NomadNetCompactPage.h"
#include "NomadNetGlyphs.h"
#include <algorithm>
#include <limits>
namespace UI::LXMF::NomadNet {
bool CompactPage::append(const std::string& value, uint32_t& offset, uint16_t& length) {
if (value.size() > std::numeric_limits<uint16_t>::max()) return false;
if (value.size() + 1 > MAX_ARENA_BYTES - std::min(_arena.size(), MAX_ARENA_BYTES)) return false;
if (_arena.size() > std::numeric_limits<uint32_t>::max() - value.size() - 1) return false;
offset = static_cast<uint32_t>(_arena.size());
length = static_cast<uint16_t>(value.size());
_arena.insert(_arena.end(), value.begin(), value.end());
_arena.push_back('\0');
return true;
}
bool CompactPage::append_display(const std::string& value, uint32_t& offset, uint16_t& length) {
if (value.size() > std::numeric_limits<uint16_t>::max()) return false;
if (value.size() + 1 > MAX_ARENA_BYTES - std::min(_arena.size(), MAX_ARENA_BYTES)) return false;
offset = static_cast<uint32_t>(_arena.size());
struct Context { ExternalVector<char>* arena; std::size_t bytes; } context{&_arena, 0};
visit_display_text(value, [](const char* bytes, std::size_t count, void* opaque) {
auto* state = static_cast<Context*>(opaque);
state->arena->insert(state->arena->end(), bytes, bytes + count);
state->bytes += count;
}, &context);
if (context.bytes > std::numeric_limits<uint16_t>::max()) return false;
length = static_cast<uint16_t>(context.bytes);
_arena.push_back('\0');
return true;
}
bool CompactPage::assign(const Document& document) {
clear();
try {
const bool reserve_notice = document.truncated;
const std::size_t block_limit = MAX_BLOCKS - (reserve_notice ? 1 : 0);
const std::size_t run_limit = MAX_RUNS - (reserve_notice ? 1 : 0);
const std::size_t block_count = std::min(document.blocks.size(), block_limit);
const std::size_t link_count = std::min(document.links.size(), MAX_LINKS);
std::size_t run_count = 0;
std::size_t arena_size = 0;
for (std::size_t i = 0; i < block_count; ++i) {
const std::size_t retained = std::min(document.blocks[i].runs.size(),
run_limit - std::min(run_count, run_limit));
run_count += retained;
for (std::size_t r = 0; r < retained; ++r) {
const std::size_t bytes = document.blocks[i].runs[r].text.size() + 1;
if (bytes > MAX_ARENA_BYTES - std::min(arena_size, MAX_ARENA_BYTES)) return false;
arena_size += bytes;
}
}
for (std::size_t i = 0; i < link_count; ++i) {
const std::size_t bytes = document.links[i].target.size() +
(document.links[i].fields.empty() ? 0 : document.links[i].fields.size() + 1) + 1;
if (bytes > MAX_ARENA_BYTES - std::min(arena_size, MAX_ARENA_BYTES)) return false;
arena_size += bytes;
}
_arena.reserve(arena_size);
_blocks.reserve(block_count);
_runs.reserve(std::min(run_count, run_limit));
_links.reserve(link_count);
for (std::size_t i = 0; i < link_count; ++i) {
LinkRecord link;
std::string navigation_target = document.links[i].target;
if (!document.links[i].fields.empty()) {
navigation_target += '`';
navigation_target += document.links[i].fields;
}
if (!append(navigation_target, link.target_offset, link.target_length)) {
clear();
return false;
}
_links.push_back(link);
}
for (std::size_t i = 0; i < block_count; ++i) {
const auto& source_block = document.blocks[i];
if (!source_block.runs.empty() && _runs.size() >= run_limit) {
_truncated = true;
continue;
}
BlockRecord block;
block.first_run = static_cast<uint32_t>(_runs.size());
block.type = source_block.type;
block.depth = source_block.depth;
block.alignment = source_block.alignment;
block.divider_codepoint = source_block.divider_codepoint;
for (const auto& source_run : source_block.runs) {
if (_runs.size() >= run_limit || block.run_count == std::numeric_limits<uint16_t>::max()) {
_truncated = true;
break;
}
RunRecord run;
if (!append_display(source_run.text, run.text_offset, run.text_length)) {
clear();
return false;
}
run.link_index = source_run.link_index >= 0 &&
static_cast<std::size_t>(source_run.link_index) < _links.size()
? static_cast<int16_t>(source_run.link_index) : -1;
if (source_run.bold) run.style |= BOLD;
if (source_run.italic) run.style |= ITALIC;
if (source_run.underline) run.style |= UNDERLINE;
if (source_run.has_foreground) run.style |= HAS_FOREGROUND;
if (source_run.has_background) run.style |= HAS_BACKGROUND;
run.foreground = source_run.foreground;
run.background = source_run.background;
_runs.push_back(run);
++block.run_count;
}
_blocks.push_back(block);
}
_has_background = document.has_background;
_background = document.background;
_has_foreground = document.has_foreground;
_foreground = document.foreground;
_truncated = _truncated || document.truncated || document.blocks.size() > block_count ||
document.links.size() > link_count;
_unsupported = document.unsupported;
return true;
} catch (const std::bad_alloc&) {
clear();
return false;
}
}
void CompactPage::clear() {
ExternalVector<char>().swap(_arena);
ExternalVector<BlockRecord>().swap(_blocks);
ExternalVector<RunRecord>().swap(_runs);
ExternalVector<LinkRecord>().swap(_links);
_has_background = false;
_background = 0;
_has_foreground = false;
_foreground = 0;
_truncated = false;
_unsupported = false;
}
bool CompactPage::append_notice(const std::string& value) {
if (value.size() > MAX_NOTICE_BYTES ||
value.size() + 1 > MAX_ARENA_BYTES - std::min(_arena.size(), MAX_ARENA_BYTES))
return false;
if (_blocks.size() >= MAX_BLOCKS) {
const BlockRecord removed = _blocks.back();
const std::size_t first = std::min<std::size_t>(removed.first_run, _runs.size());
_runs.resize(first);
_blocks.pop_back();
}
if (_runs.size() >= MAX_RUNS) {
std::size_t owner = _blocks.size();
while (owner > 0 && _blocks[owner - 1].run_count == 0) --owner;
if (owner == 0) return false;
_runs.pop_back();
--_blocks[owner - 1].run_count;
for (std::size_t i = owner; i < _blocks.size(); ++i)
_blocks[i].first_run = static_cast<uint32_t>(_runs.size());
}
try {
RunRecord run;
if (!append(value, run.text_offset, run.text_length)) return false;
BlockRecord block;
block.first_run = static_cast<uint32_t>(_runs.size());
block.run_count = 1;
block.type = BlockType::UNSUPPORTED;
_runs.push_back(run);
_blocks.push_back(block);
return true;
} catch (const std::bad_alloc&) {
clear();
return false;
}
}
CompactPage::TextView CompactPage::text(const RunRecord& run) const {
if (run.text_offset > _arena.size() || run.text_length > _arena.size() - run.text_offset) return {};
return {_arena.data() + run.text_offset, run.text_length};
}
CompactPage::TextView CompactPage::target(std::size_t index) const {
if (index >= _links.size()) return {};
const auto& link = _links[index];
if (link.target_offset > _arena.size() || link.target_length > _arena.size() - link.target_offset) return {};
return {_arena.data() + link.target_offset, link.target_length};
}
} // namespace UI::LXMF::NomadNet