pub(crate) mod codec;
pub(crate) mod fonts;
pub(crate) mod impls_core;
pub(crate) mod impls_plot;
pub(crate) mod impls_scale;
pub(crate) mod impls_theme;
pub(crate) mod intern;
#[cfg(feature = "document-read")]
mod read;
pub(crate) mod wire;
#[cfg(feature = "document-write")]
mod write;
#[cfg(feature = "document-read")]
pub use read::{GeomFactory, ReadContext};
#[cfg(feature = "document-write")]
pub use write::{unsupported_items, UnsupportedItem, WriteOptions};
pub const FORMAT_VERSION_MAJOR: u16 = wire::VERSION_MAJOR;
#[cfg(feature = "document-write")]
pub const FORMAT_VERSION_MINOR: u16 = wire::VERSION_MINOR;
#[derive(Debug, thiserror::Error)]
pub enum DocumentError {
#[error("not a hephaestus plot document (bad magic)")]
BadMagic,
#[error(
"plot document format version {found} is newer than this build reads (supports up to {supported})"
)]
UnsupportedVersion {
found: u16,
supported: u16,
},
#[error("plot document ended mid-value at offset {offset}: wanted {wanted} more bytes, {available} left")]
UnexpectedEof {
offset: usize,
wanted: usize,
available: usize,
},
#[error("plot document has invalid {type_name} discriminant {tag} at offset {offset}")]
BadDiscriminant {
type_name: &'static str,
tag: u64,
offset: usize,
},
#[error("plot document has invalid UTF-8 in a string at offset {offset}")]
BadUtf8 {
offset: usize,
},
#[error("plot document has a malformed varint at offset {offset}")]
BadVarint {
offset: usize,
},
#[error("plot document holds a geom of unknown kind {kind:?}")]
UnknownGeom {
kind: String,
},
#[error("plot document is missing its {tag} chunk")]
MissingChunk {
tag: &'static str,
},
#[cfg(feature = "document-write")]
#[error("plot cannot be written as a document: {}", .0.iter().map(ToString::to_string).collect::<Vec<_>>().join("; "))]
Unsupported(Vec<UnsupportedItem>),
#[error("plot document holds an invalid {what}: {why}")]
Invalid {
what: &'static str,
why: String,
},
}
#[cfg(feature = "document-write")]
pub fn write_composition(
comp: &crate::plot::PlotComposition,
opts: &WriteOptions,
) -> Result<Vec<u8>, DocumentError> {
use codec::{Encode, Writer};
let problems = unsupported_items(comp);
if !problems.is_empty() && !opts.lossy {
return Err(DocumentError::Unsupported(problems));
}
let mut w = Writer::new();
let theme = w.detached(|w| comp.theme.as_ref().encode(w));
let scales = w.detached(|w| comp.scales.encode(w));
let composition = w.detached(|w| {
comp.template.encode(w);
let mut ids: Vec<&String> = comp.chrome.keys().collect();
ids.sort_unstable();
w.varint(ids.len() as u64);
for id in ids {
id.encode(w);
comp.chrome[id].encode(w);
}
comp.chrome_order.encode(w);
});
let plots = w.detached(|w| {
w.varint(comp.plot_order.len() as u64);
for patch in &comp.plot_order {
patch.encode(w);
let list = comp.plots.get(patch).map(Vec::as_slice).unwrap_or(&[]);
w.varint(list.len() as u64);
for plot in list {
plot.encode(w);
}
}
});
let sheets = w.detached(|w| {
let table = w.tables().sheets().to_vec();
w.varint(table.len() as u64);
for sheet in &table {
impls_theme::encode_sheet(sheet, w);
}
});
let geometry = w.detached(|w| {
let table = w.tables().geometries().to_vec();
w.varint(table.len() as u64);
for g in &table {
g.as_ref().encode(w);
}
});
let strings = w.detached(|w| {
let table = w.tables().strings().to_vec();
w.varint(table.len() as u64);
for s in &table {
w.str(s);
}
});
let font_bytes = w.detached(|w| {
let embedded = if opts.embed_fonts {
let (named, generics) = fonts::referenced_families(comp);
fonts::collect(&named, &generics)
} else {
fonts::EmbeddedFonts::default()
};
embedded.encode(w);
});
let head = w.detached(|w| {
comp.root_id.encode(w);
opts.background.encode(w);
opts.size_hint.encode(w);
opts.dpi_hint.encode(w);
});
wire::assemble(
&mut w,
&[
(wire::CHUNK_HEAD, head),
(wire::CHUNK_FONTS, font_bytes),
(wire::CHUNK_STRINGS, strings),
(wire::CHUNK_GEOMETRY, geometry),
(wire::CHUNK_SHEETS, sheets),
(wire::CHUNK_THEME, theme),
(wire::CHUNK_SCALES, scales),
(wire::CHUNK_COMPOSITION, composition),
(wire::CHUNK_PLOTS, plots),
],
);
Ok(w.finish())
}
#[cfg(feature = "document-read")]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DocumentHints {
pub background: Option<crate::color::Color>,
pub size: Option<(f64, f64)>,
pub dpi: Option<f64>,
}
#[cfg(feature = "document-read")]
pub fn read_hints(bytes: &[u8]) -> Result<DocumentHints, DocumentError> {
let chunks = wire::parse(bytes)?;
let body = wire::chunk(&chunks, wire::CHUNK_HEAD)
.ok_or(DocumentError::MissingChunk { tag: "HEAD" })?;
decode_head(body, read::default_context()).map(|(_, hints)| hints)
}
#[cfg(feature = "document-read")]
fn decode_head(body: &[u8], ctx: &ReadContext) -> Result<(String, DocumentHints), DocumentError> {
use codec::{Decode, Reader};
let mut r = Reader::with_context(body, ctx);
let root_id = String::decode(&mut r)?;
let hints = DocumentHints {
background: Option::<crate::color::Color>::decode(&mut r)?,
size: Option::<(f64, f64)>::decode(&mut r)?,
dpi: Option::<f64>::decode(&mut r)?,
};
Ok((root_id, hints))
}
#[cfg(feature = "document-read")]
pub fn read_composition(
bytes: &[u8],
ctx: &ReadContext,
) -> Result<crate::plot::PlotComposition, DocumentError> {
use codec::{Decode, Reader};
let chunks = wire::parse(bytes)?;
let mut tables = intern::ReadTables::default();
fn required<'a>(
chunks: &[wire::Chunk<'a>],
tag: &'static [u8; 4],
) -> Result<&'a [u8], DocumentError> {
wire::chunk(chunks, tag).ok_or(DocumentError::MissingChunk {
tag: std::str::from_utf8(tag).unwrap_or("????"),
})
}
if let Some(body) = wire::chunk(&chunks, wire::CHUNK_FONTS) {
let mut r = Reader::with_context(body, ctx);
fonts::register(&fonts::EmbeddedFonts::decode(&mut r)?);
}
{
let body = required(&chunks, wire::CHUNK_STRINGS)?;
let mut r = Reader::with_context(body, ctx);
let n = r.count()?;
let mut strings = Vec::with_capacity(n);
for _ in 0..n {
strings.push(std::sync::Arc::from(r.str()?));
}
tables.set_strings(strings);
}
{
let body = required(&chunks, wire::CHUNK_GEOMETRY)?;
let mut r = Reader::with_tables(body, ctx, tables.clone());
let n = r.count()?;
let mut geometries = Vec::with_capacity(n);
for _ in 0..n {
geometries.push(std::sync::Arc::new(
crate::scales::geometry::Geometry::decode(&mut r)?,
));
}
tables.set_geometries(geometries);
}
{
let body = required(&chunks, wire::CHUNK_SHEETS)?;
let mut r = Reader::with_tables(body, ctx, tables.clone());
let n = r.count()?;
let mut sheets = Vec::with_capacity(n);
for _ in 0..n {
sheets.push(std::sync::Arc::new(impls_theme::decode_sheet(&mut r)?));
}
tables.set_sheets(sheets);
}
let (root_id, _hints) = decode_head(required(&chunks, wire::CHUNK_HEAD)?, ctx)?;
let theme = {
let body = required(&chunks, wire::CHUNK_THEME)?;
let mut r = Reader::with_tables(body, ctx, tables.clone());
crate::plot::theme::Theme::decode(&mut r)?
};
let scales = {
let body = required(&chunks, wire::CHUNK_SCALES)?;
let mut r = Reader::with_tables(body, ctx, tables.clone());
crate::plot::ScaleRegistry::decode(&mut r)?
};
let (template, chrome, chrome_order) = {
let body = required(&chunks, wire::CHUNK_COMPOSITION)?;
let mut r = Reader::with_tables(body, ctx, tables.clone());
let template = crate::plot::composition::CompositionTemplate::decode(&mut r)?;
let n = r.count()?;
let mut chrome = std::collections::HashMap::with_capacity(n);
for _ in 0..n {
let id = String::decode(&mut r)?;
chrome.insert(
id,
crate::plot::composition::CompositionChrome::decode(&mut r)?,
);
}
let order = Vec::<String>::decode(&mut r)?;
(template, chrome, order)
};
let bare = template.bare();
let (plots, plot_order) = {
let body = required(&chunks, wire::CHUNK_PLOTS)?;
let mut r = Reader::with_tables(body, ctx, tables.clone());
let n = r.count()?;
let mut plots: std::collections::HashMap<String, Vec<crate::plot::Plot>> =
std::collections::HashMap::with_capacity(n);
let mut order = Vec::with_capacity(n);
for _ in 0..n {
let patch = String::decode(&mut r)?;
let count = r.count()?;
let mut list = Vec::with_capacity(count);
for _ in 0..count {
list.push(impls_plot::decode_plot(&mut r, &bare)?);
}
order.push(patch.clone());
plots.insert(patch, list);
}
(plots, order)
};
Ok(crate::plot::PlotComposition::from_document(
template,
root_id,
scales,
std::sync::Arc::new(theme),
plots,
plot_order,
chrome,
chrome_order,
))
}