use std::io::{self, Read};
use byteorder::{LittleEndian, ReadBytesExt};
use colors::ColorTable;
use commands::Command;
use header::{CoordinateRange, Scale, TvgHeader};
use thiserror::Error;
mod colors;
mod commands;
mod header;
mod path;
pub use colors::{Color, Rgb565, Rgba16, Rgba8888, RgbaF32};
pub use header::SUPPORTED_VERSION;
pub struct TvgFile<C: Color> {
header: TvgHeader,
color_table: ColorTable<C>,
commands: Box<[Command]>,
}
impl<C: Color + std::fmt::Debug> TvgFile<C> {
/// Read a TinyVG file.
pub fn read_from(reader: &mut impl Read) -> Result<Self, TvgError> {
let header = TvgHeader::read(reader)?;
let color_table =
ColorTable::read_from_encoding(reader, header.color_count(), header.color_encoding())?;
let mut commands = Vec::new();
loop {
let command = Command::read(reader, &header)?;
commands.push(command.clone());
if command.is_end_of_document() {
break;
}
}
Ok(Self {
header,
color_table,
commands: commands.into_boxed_slice(),
})
}
/// Read a TinyVG file. If a Custom color encoding if found, use the specified encoding.
pub fn read_with_custom_encoding<Custom: Color>(
reader: &mut impl Read,
) -> Result<Self, TvgError> {
let header = TvgHeader::read(reader)?;
let color_table = ColorTable::read_from_encoding_with_custom::<Custom>(
reader,
header.color_count(),
header.color_encoding(),
)?;
let mut commands = Vec::new();
loop {
let command = Command::read(reader, &header)?;
commands.push(command.clone());
if command.is_end_of_document() {
break;
}
}
Ok(Self {
header,
color_table,
commands: commands.into_boxed_slice(),
})
}
}
#[derive(Debug, Error)]
pub enum TvgError {
#[error("Not a valid TVG file. The magic number was invalid.")]
InvalidFile,
#[error("Expected version 1, but found version {}", .0)]
UnsupportedVersion(u8),
#[error("Found a coordinate range with an index of 3, which is invalid")]
InvalidCoordinateRange,
#[error("Found a Custom color encoding, which is unsupported")]
UnsupportedColorEncoding,
#[error("Found a command with an index of {}, which is invalid", .0)]
InvalidCommand(u8),
#[error("Found a style kind with an index of 3, which is invalid")]
InvalidStyleKind,
#[error("Polygons must have at least 3 points, found {}", .0)]
InvalidPolygon(u32),
#[error("The end of the document must have a `prim_style_kind` value of 0. Found {}", .0)]
InvalidEndOfDocument(u8),
#[error("{}", .0)]
IoError(#[from] io::Error),
}
trait Decode: Sized {
fn read(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self>;
fn read_multiple(
reader: &mut impl Read,
header: &TvgHeader,
count: u32,
) -> io::Result<Box<[Self]>> {
let mut vec = Vec::with_capacity(count as usize);
for _ in 0..count {
vec.push(Self::read(reader, header)?);
}
Ok(vec.into_boxed_slice())
}
}
/// The unit is the common type for both positions and sizes in the vector
/// graphic. It is encoded as a signed integer with a configurable amount of
/// bits (see [`CoordinateRange`]) and fractional bits.
fn read_unit(reader: &mut impl Read, header: &TvgHeader) -> io::Result<f64> {
let value = match header.coordinate_range() {
CoordinateRange::Reduced => reader.read_i8()? as i32,
CoordinateRange::Default => reader.read_i16::<LittleEndian>()? as i32,
CoordinateRange::Enhanced => reader.read_i32::<LittleEndian>()?,
};
let fractional = match header.scale() {
Scale::_1 => 1.0,
Scale::_2 => 2.0,
Scale::_4 => 4.0,
Scale::_8 => 8.0,
Scale::_16 => 16.0,
Scale::_32 => 32.0,
Scale::_64 => 64.0,
Scale::_128 => 128.0,
Scale::_256 => 256.0,
Scale::_512 => 512.0,
Scale::_1024 => 1024.0,
Scale::_2048 => 2048.0,
Scale::_4096 => 4096.0,
Scale::_8192 => 8192.0,
Scale::_16384 => 16_384.0,
Scale::_32768 => 32_768.0,
};
Ok((value as f64) / fractional)
}
/// A X and Y coordinate pair.
#[derive(Debug, Clone, Copy)]
pub struct Point {
/// Horizontal distance of the point to the origin.
x: f64,
/// Vertical distance of the point to the origin.
y: f64,
}
impl Decode for Point {
fn read(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> {
Ok(Self {
x: read_unit(reader, header)?,
y: read_unit(reader, header)?,
})
}
}
fn read_varuint(reader: &mut impl Read) -> io::Result<u32> {
let mut count = 0;
let mut result = 0;
loop {
let byte = reader.read_u8()? as u32;
let value = (byte & 0x7F) << (7 * count);
result |= value;
if (byte & 0x80) == 0 {
break;
}
count += 1;
}
Ok(result)
}
|