use std::io::{self, Read}; use byteorder::{LittleEndian, ReadBytesExt}; use colors::{Color, ColorTable}; use commands::Command; use header::{CoordinateRange, Scale, TvgHeader}; use thiserror::Error; pub mod colors; mod commands; mod header; mod path; mod render; pub use header::SUPPORTED_VERSION; #[derive(Debug, Clone)] pub struct TvgFile { header: TvgHeader, color_table: ColorTable, commands: Box<[Command]>, } impl TvgFile { /// Read a TinyVG file. pub fn read_from(reader: &mut impl Read) -> Result { 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( reader: &mut impl Read, ) -> Result { let header = TvgHeader::read(reader)?; let color_table = ColorTable::read_from_encoding_with_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("All padding bits must be zero. Found {}.", .0)] NonZeroPadding(u8), #[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; fn read_multiple( reader: &mut impl Read, header: &TvgHeader, count: u32, ) -> io::Result> { 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 { let value = match header.coordinate_range() { CoordinateRange::Reduced => reader.read_i8()? as i32, CoordinateRange::Default => reader.read_i16::()? as i32, CoordinateRange::Enhanced => reader.read_i32::()?, }; 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) } fn read_varuint(reader: &mut impl Read) -> io::Result { 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) }