blob: fee4777dac2c8d340d3ce5332a0ca70986add1bc [file]
#include "rive/assets/manifest_asset.hpp"
#include "rive/factory.hpp"
#include "rive/core/binary_reader.hpp"
#include "rive/span.hpp"
#include "rive/manifest_sections.hpp"
#include <cstring>
using namespace rive;
const std::string ManifestAsset::empty;
const std::vector<uint32_t> ManifestAsset::emptyIntVector;
bool ManifestAsset::decodeNames(BinaryReader& reader)
{
// Read count of names
uint64_t count = reader.readVarUint64();
if (reader.hasError())
{
return false;
}
// Read all name entries
for (uint64_t i = 0; i < count; i++)
{
int id = static_cast<int>(reader.readVarUint64());
if (reader.hasError())
{
return false;
}
std::string value = reader.readString();
if (reader.hasError())
{
return false;
}
m_names[id] = value;
}
return true;
}
bool ManifestAsset::decodePaths(BinaryReader& reader)
{
// Read count of paths
uint64_t count = reader.readVarUint64();
if (reader.hasError())
{
return false;
}
// Read all path entries
for (uint64_t i = 0; i < count; i++)
{
int id = static_cast<int>(reader.readVarUint64());
if (reader.hasError())
{
return false;
}
int pathLength = static_cast<int>(reader.readVarUint64());
if (reader.hasError())
{
return false;
}
std::vector<uint32_t> path;
for (uint64_t j = 0; j < pathLength; j++)
{
int pathId = static_cast<uint32_t>(reader.readVarUint64());
path.push_back(pathId);
}
m_paths[id] = path;
if (reader.hasError())
{
return false;
}
}
return true;
}
bool ManifestAsset::decodeWatermark(BinaryReader& reader, uint64_t sectionSize)
{
const uint8_t* sectionStart = reader.position();
uint64_t version = reader.readVarUint64();
if (reader.hasError())
{
return false;
}
if (version == watermarkSectionVersion)
{
uint64_t flags = reader.readVarUint64();
if (reader.hasError())
{
return false;
}
// Read as uint32_t rather than truncating a uint64_t: an index that
// does not fit is a malformed manifest, and silently wrapping it (2^32
// becomes 0) would attach some unrelated artboard as the watermark.
// readVarUintAs sets the reader's range error, which the check below
// turns into a rejected section.
uint32_t artboardIndex = reader.readVarUintAs<uint32_t>();
if (reader.hasError())
{
return false;
}
m_hasWatermark = (flags & watermarkFlagEnabled) != 0;
m_watermarkArtboardIndex = artboardIndex;
}
// An unrecognized version leaves the watermark unset, but the section is
// still consumed below so the rest of the manifest keeps parsing.
// Unlike names and paths, this section tolerates trailing bytes: a newer
// exporter may append fields to it, and decode() requires each known
// section be consumed exactly. Swallow whatever is left.
size_t bytesRead = static_cast<size_t>(reader.position() - sectionStart);
if (bytesRead > sectionSize)
{
return false;
}
reader.readBytes(sectionSize - bytesRead);
return !reader.hasError();
}
bool ManifestAsset::decode(SimpleArray<uint8_t>& bytes, Factory* factory)
{
if (bytes.empty())
{
return true;
}
BinaryReader reader(Span<const uint8_t>(bytes.data(), bytes.size()));
while (!reader.reachedEnd())
{
// Read section enum value
uint64_t sectionValue = reader.readVarUint64();
if (reader.hasError())
{
return false;
}
// Read section size
uint64_t sectionSize = reader.readVarUint64();
if (reader.hasError())
{
return false;
}
// Store the position before reading the section content
const uint8_t* sectionStart = reader.position();
// Compare the raw id, never a narrowed one: ManifestSections has an
// unsigned char base, so casting down aliases every id mod 256 onto a
// known section (258 lands on watermark, 256 on names, 257 on paths).
// That both misreads malformed input and defeats the skip-unknown
// branch below, which is what lets a newer exporter add sections.
if (sectionValue == static_cast<uint64_t>(ManifestSections::names))
{
if (!decodeNames(reader))
{
return false;
}
}
else if (sectionValue == static_cast<uint64_t>(ManifestSections::paths))
{
if (!decodePaths(reader))
{
return false;
}
}
else if (sectionValue ==
static_cast<uint64_t>(ManifestSections::watermark))
{
if (!decodeWatermark(reader, sectionSize))
{
return false;
}
}
else
{
// Unknown section - skip the specified number of bytes
reader.readBytes(static_cast<size_t>(sectionSize));
if (reader.hasError())
{
return false;
}
continue;
}
// Verify we read exactly the section size
size_t bytesRead = reader.position() - sectionStart;
if (bytesRead != sectionSize)
{
return false;
}
}
return true;
}
std::string ManifestAsset::fileExtension() const { return "man"; }
const std::string& ManifestAsset::resolveName(int id)
{
auto it = m_names.find(id);
if (it != m_names.end())
{
return it->second;
}
return empty;
}
int ManifestAsset::nameId(const char* name, size_t length) const
{
for (const auto& entry : m_names)
{
if (entry.second.size() == length &&
memcmp(entry.second.data(), name, length) == 0)
{
return entry.first;
}
}
return -1;
}
const std::vector<uint32_t>& ManifestAsset::resolvePath(int id)
{
auto it = m_paths.find(id);
if (it != m_paths.end())
{
return it->second;
}
return emptyIntVector;
}