summaryrefslogtreecommitdiff
path: root/alligator_tvg/src
diff options
context:
space:
mode:
Diffstat (limited to 'alligator_tvg/src')
-rw-r--r--alligator_tvg/src/colors.rs333
-rw-r--r--alligator_tvg/src/commands.rs613
-rw-r--r--alligator_tvg/src/header.rs149
-rw-r--r--alligator_tvg/src/lib.rs151
-rw-r--r--alligator_tvg/src/path.rs294
5 files changed, 0 insertions, 1540 deletions
diff --git a/alligator_tvg/src/colors.rs b/alligator_tvg/src/colors.rs
deleted file mode 100644
index 10bc41c..0000000
--- a/alligator_tvg/src/colors.rs
+++ /dev/null
@@ -1,333 +0,0 @@
-use std::io::{self, Read};
-
-use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
-use num_enum::TryFromPrimitive;
-
-/// The color table encodes the palette for this file.
-///
-/// It’s binary content is defined by the `color_encoding` field in the header.
-/// For the three defined color encodings, each will yield a list of
-/// `color_count` RGBA tuples.
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ColorTable<C: Color> {
- colors: Box<[C]>,
-}
-
-impl<C: Color> ColorTable<C> {
- /// Read in one encoding, and convert it to another
- pub fn read_from_encoding(
- reader: &mut impl Read,
- color_count: u32,
- encoding: ColorEncoding,
- ) -> io::Result<Self> {
- Ok(match encoding {
- ColorEncoding::Rgba8888 => (&ColorTable::<Rgba8888>::read(reader, color_count)?).into(),
- ColorEncoding::Rgb565 => (&ColorTable::<Rgb565>::read(reader, color_count)?).into(),
- ColorEncoding::RgbaF32 => (&ColorTable::<RgbaF32>::read(reader, color_count)?).into(),
- ColorEncoding::Custom => (&ColorTable::<Rgba16>::read(reader, color_count)?).into(),
- })
- }
-
- /// Parse a color table.
- fn read(reader: &mut impl Read, color_count: u32) -> io::Result<Self> {
- let mut colors = Vec::with_capacity(color_count as usize);
- for _ in 0..color_count {
- colors.push(C::parse_bytes(reader)?);
- }
-
- let colors = colors.into_boxed_slice();
- Ok(Self { colors })
- }
-
- /// Returns the number of colors in the table.
- fn len(&self) -> usize {
- self.colors.len()
- }
-
- /// Returns a reference to a color, or `None` if out-of-bounds.
- fn get(&self, index: usize) -> Option<&C> {
- self.colors.get(index)
- }
-
- fn iter(&self) -> impl Iterator<Item = &C> {
- self.colors.iter()
- }
-}
-
-impl ColorTable<Rgba16> {}
-
-impl<Old: Color, New: Color> From<&ColorTable<Old>> for ColorTable<New> {
- fn from(value: &ColorTable<Old>) -> Self {
- let mut colors = Vec::with_capacity(value.len());
-
- for color in value.iter() {
- let r = color.red_u16();
- let g = color.green_u16();
- let b = color.blue_u16();
- let a = color.alpha_u16();
- colors.push(New::from_rgba16_lossy(r, g, b, a));
- }
-
- let colors = colors.into_boxed_slice();
- Self { colors }
- }
-}
-
-/// The color encoding defines which format the colors in the color table will
-/// have.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)]
-#[repr(u8)]
-pub enum ColorEncoding {
- /// Each color is a 4-tuple (red, green, blue, alpha) of bytes with the
- /// color channels encoded in sRGB and the alpha as linear alpha.
- Rgba8888 = 0,
- /// Each color is encoded as a 3-tuple (red, green, blue) with 16 bits per
- /// color.
- ///
- /// While red and blue both use 5 bits, the green channel uses 6 bits. Red
- /// uses bit range 0...4, green bits 5...10 and blue bits 11...15. This
- /// color also uses the sRGB color space.
- Rgb565 = 1,
- /// Each color is a 4-tuple (red, green, blue, alpha) of binary32 IEEE 754
- /// floating point value with the color channels encoded in scRGB and the
- /// alpha as linear alpha. A color value of 1.0 is full intensity, while a
- /// value of 0.0 is zero intensity.
- RgbaF32 = 2,
- /// The custom color encoding is defined *undefined*. The information how
- /// these colors are encoded must be implemented via external means.
- Custom = 3,
-}
-
-pub trait Color: Sized {
- /// The size of the color's representation in bits
- const SIZE: usize;
-
- /// Attempt to read the color. Returns `Err` if an error occurred while
- /// attempting to read [`SIZE`] bytes from `bytes`.
- fn parse_bytes(reader: &mut impl Read) -> io::Result<Self>;
-
- /// Convert from the RGBA16 format to this format. This may be lossy.
- fn from_rgba16_lossy(red: u16, green: u16, blue: u16, alpha: u16) -> Self;
-
- fn red_u16(&self) -> u16;
- fn blue_u16(&self) -> u16;
- fn green_u16(&self) -> u16;
- fn alpha_u16(&self) -> u16;
-}
-
-/// Each color value is encoded as a sequence of four bytes.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-struct Rgba8888 {
- /// Red color channel between 0 and 100% intensity, mapped to byte values 0
- /// to 255.
- red: u8,
- /// Green color channel between 0 and 100% intensity, mapped to byte values
- /// 0 to 255.
- green: u8,
- /// Blue color channel between 0 and 100% intensity, mapped to byte values
- /// 0 to 255.
- blue: u8,
- /// Transparency channel between 0 and 100% transparency, mapped to byte
- /// values 0 to 255.
- alpha: u8,
-}
-
-impl Color for Rgba8888 {
- const SIZE: usize = 4;
-
- fn parse_bytes(reader: &mut impl Read) -> io::Result<Self> {
- Ok(Self {
- red: reader.read_u8()?,
- green: reader.read_u8()?,
- blue: reader.read_u8()?,
- alpha: reader.read_u8()?,
- })
- }
-
- fn from_rgba16_lossy(red: u16, green: u16, blue: u16, alpha: u16) -> Self {
- Self {
- red: (red >> 8) as u8,
- green: (green >> 8) as u8,
- blue: (blue >> 8) as u8,
- alpha: (alpha >> 8) as u8,
- }
- }
-
- fn red_u16(&self) -> u16 {
- (self.red as u16) << 8
- }
-
- fn green_u16(&self) -> u16 {
- (self.green as u16) << 8
- }
-
- fn blue_u16(&self) -> u16 {
- (self.blue as u16) << 8
- }
-
- fn alpha_u16(&self) -> u16 {
- (self.alpha as u16) << 8
- }
-}
-
-/// Each color value is encoded as a sequence of 2 bytes.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-struct Rgb565 {
- /// Red color channel between 0 and 100% intensity, mapped to integer
- /// values 0 to 31.
- red: u8,
- /// Green color channel between 0 and 100% intensity, mapped to integer
- /// values 0 to 63.
- green: u8,
- /// Blue color channel between 0 and 100% intensity, mapped to integer
- /// values 0 to 31.
- blue: u8,
-}
-
-impl Color for Rgb565 {
- const SIZE: usize = 2;
-
- fn parse_bytes(reader: &mut impl Read) -> io::Result<Self> {
- let color = reader.read_u16::<LittleEndian>()?;
-
- let red = ((color & 0x001F) << 3) as u8;
- let green = ((color & 0x07E0) >> 3) as u8;
- let blue = ((color & 0xF800) >> 8) as u8;
-
- Ok(Self { red, blue, green })
- }
-
- fn from_rgba16_lossy(red: u16, green: u16, blue: u16, _a: u16) -> Self {
- Self {
- red: (red >> 11) as u8,
- green: (green >> 10) as u8,
- blue: (blue >> 11) as u8,
- }
- }
-
- fn red_u16(&self) -> u16 {
- (self.red as u16) << 11
- }
-
- fn green_u16(&self) -> u16 {
- (self.green as u16) << 11
- }
-
- fn blue_u16(&self) -> u16 {
- (self.blue as u16) << 10
- }
-
- fn alpha_u16(&self) -> u16 {
- 0
- }
-}
-
-/// Each color value is encoded as a sequence of 16 bytes.
-#[derive(Debug, Clone, Copy, PartialEq)]
-struct RgbaF32 {
- /// Red color channel, using 0.0 for 0% intensity and 1.0 for 100%
- /// intensity.
- red: f32,
- /// Green color channel, using 0.0 for 0% intensity and 1.0 for 100%
- /// intensity.
- green: f32,
- /// Blue color channel, using 0.0 for 0% intensity and 1.0 for 100%
- /// intensity.
- blue: f32,
- /// Transparency channel between 0 and 100% transparency, mapped to byte
- /// values 0.0 to 1.0.
- alpha: f32,
-}
-
-impl Color for RgbaF32 {
- const SIZE: usize = 16;
-
- fn parse_bytes(reader: &mut impl Read) -> io::Result<Self> {
- Ok(Self {
- red: reader.read_f32::<LittleEndian>()?,
- green: reader.read_f32::<LittleEndian>()?,
- blue: reader.read_f32::<LittleEndian>()?,
- alpha: reader.read_f32::<LittleEndian>()?,
- })
- }
-
- fn from_rgba16_lossy(red: u16, green: u16, blue: u16, alpha: u16) -> Self {
- Self {
- red: (red as f32) / (u16::MAX as f32),
- green: (green as f32) / (u16::MAX as f32),
- blue: (blue as f32) / (u16::MAX as f32),
- alpha: (alpha as f32) / (u16::MAX as f32),
- }
- }
-
- fn red_u16(&self) -> u16 {
- (self.red * (u16::MAX as f32)) as u16
- }
-
- fn green_u16(&self) -> u16 {
- (self.green * (u16::MAX as f32)) as u16
- }
-
- fn blue_u16(&self) -> u16 {
- (self.blue * (u16::MAX as f32)) as u16
- }
-
- fn alpha_u16(&self) -> u16 {
- (self.alpha * (u16::MAX as f32)) as u16
- }
-}
-
-/// Each color value is encoded as a sequence of 8 bytes.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct Rgba16 {
- /// Red color channel between 0 and 100% intensity, mapped to a big-endian
- /// 16-bit integer.
- red: u16,
- /// Green color channel between 0 and 100% intensity, mapped to a
- /// big-endian 16-bit integer.
- green: u16,
- /// Blue color channel between 0 and 100% intensity, mapped to a big-endian
- /// 16-bit integer.
- blue: u16,
- /// Transparency channel between 0 and 100% intensity, mapped to a
- /// big-endian 16-bit integer.
- alpha: u16,
-}
-
-impl Color for Rgba16 {
- const SIZE: usize = 8;
-
- fn parse_bytes(reader: &mut impl Read) -> io::Result<Self> {
- Ok(Self {
- red: reader.read_u16::<BigEndian>()?,
- green: reader.read_u16::<BigEndian>()?,
- blue: reader.read_u16::<BigEndian>()?,
- alpha: reader.read_u16::<BigEndian>()?,
- })
- }
-
- fn from_rgba16_lossy(red: u16, green: u16, blue: u16, alpha: u16) -> Self {
- Self {
- red,
- green,
- blue,
- alpha,
- }
- }
-
- fn red_u16(&self) -> u16 {
- self.red
- }
-
- fn green_u16(&self) -> u16 {
- self.green
- }
-
- fn blue_u16(&self) -> u16 {
- self.blue
- }
-
- fn alpha_u16(&self) -> u16 {
- self.alpha
- }
-}
diff --git a/alligator_tvg/src/commands.rs b/alligator_tvg/src/commands.rs
deleted file mode 100644
index f316a53..0000000
--- a/alligator_tvg/src/commands.rs
+++ /dev/null
@@ -1,613 +0,0 @@
-use std::io::{self, Read};
-
-use byteorder::ReadBytesExt;
-use raise::yeet;
-
-use crate::{header::TvgHeader, path::Path, read_unit, read_varuint, Decode, Point, TvgError};
-
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct Rectangle {
- /// Horizontal distance of the left side to the origin.
- x: f64,
- /// Vertical distance of the upper side to the origin.
- y: f64,
- /// Horizontal extent of the rectangle.
- width: f64,
- /// Vertical extent of the rectangle.
- height: f64,
-}
-
-impl Decode for Rectangle {
- fn read(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> {
- Ok(Self {
- x: read_unit(reader, header)?,
- y: read_unit(reader, header)?,
- width: read_unit(reader, header)?,
- height: read_unit(reader, header)?,
- })
- }
-}
-
-#[derive(Debug, Clone, Copy)]
-pub struct Line {
- /// Start point of the line.
- start: Point,
- /// End point of the line.
- end: Point,
-}
-
-impl Decode for Line {
- fn read(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> {
- Ok(Self {
- start: Point::read(reader, header)?,
- end: Point::read(reader, header)?,
- })
- }
-}
-
-/// A command that can be encoded.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-#[repr(u8)]
-enum CommandName {
- /// Determines end of file.
- EndOfDocument = 0,
- /// Fills an N-gon.
- FillPolygon = 1,
- /// Fills a set of [`Rectangle`]s.
- FillRectangles = 2,
- /// Fills a free-form [`Path`].
- FillPath = 3,
- /// Draws a set of lines.
- DrawLines = 4,
- /// Draws the outline of a polygon.
- DrawLineLoop = 5,
- /// Draws a list of end-to-end lines.
- DrawLineStrip = 6,
- /// Draws a free-form [`Path`].
- DrawLinePath = 7,
- /// Draws a filled polygon with an outline.
- OutlineFillPolygon = 8,
- /// Draws several filled [`Rectangle`]s with an outline.
- OutlineFillRectangles = 9,
- /// This command combines the [`FillPath`] and [`DrawLinePath`] command
- /// into one.
- OutlineFillPath = 10,
-}
-
-impl TryFrom<u8> for CommandName {
- type Error = TvgError;
-
- fn try_from(value: u8) -> Result<Self, Self::Error> {
- match value {
- 0 => Ok(Self::EndOfDocument),
- 1 => Ok(Self::FillPolygon),
- 2 => Ok(Self::FillRectangles),
- 3 => Ok(Self::FillPath),
- 4 => Ok(Self::DrawLines),
- 5 => Ok(Self::DrawLineLoop),
- 6 => Ok(Self::DrawLineStrip),
- 7 => Ok(Self::DrawLinePath),
- 8 => Ok(Self::OutlineFillPolygon),
- 9 => Ok(Self::OutlineFillRectangles),
- 10 => Ok(Self::OutlineFillPath),
- v => Err(TvgError::InvalidCommand(v)),
- }
- }
-}
-
-/// The type of style the command uses as a primary style.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-#[repr(u8)]
-pub enum StyleKind {
- /// The shape is uniformly colored with a single color.
- FlatColored = 0,
- /// The shape is colored with a linear gradient.
- LinearGradient = 1,
- /// The shape is colored with a radial gradient.
- RadialGradient = 2,
-}
-
-impl TryFrom<u8> for StyleKind {
- type Error = TvgError;
-
- fn try_from(value: u8) -> Result<Self, Self::Error> {
- match value {
- 0 => Ok(Self::FlatColored),
- 1 => Ok(Self::LinearGradient),
- 2 => Ok(Self::RadialGradient),
- _ => Err(TvgError::InvalidStyleKind),
- }
- }
-}
-
-/// The style kind, along with the colors and points used to render the style.
-#[derive(Debug, Clone, Copy)]
-pub enum Style {
- /// The shape is uniformly colored with the color at `color_index` in the
- /// [`ColorTable`].
- FlatColored {
- /// The index in the [`ColorTable`].
- color_index: u32,
- },
- /// The gradient is formed by a mental line between `point_0` and
- /// `point_1`.
- ///
- /// The color at `point_0` is the color at `color_index_0` in the color
- /// table. The color at `point_1` is the color at `color_index_1` in the
- /// [`ColorTable`]. On the line, the color is interpolated between the two
- /// points. Each point that is not on the line is orthogonally projected to
- /// the line and the color at that point is sampled. Points that are not
- /// projectable onto the line have either the color at `point_0` if they
- /// are closed to `point_0` or vice versa for `point_1`.
- LinearGradient {
- /// The start point of the gradient.
- point_0: Point,
- /// The end point of the gradient.
- point_1: Point,
- /// The color at [`point_0`].
- color_index_0: u32,
- /// The color at [`point_1`].
- color_index_1: u32,
- },
- /// The gradient is formed by a mental circle with the center at `point_0`
- /// and `point_1` being somewhere on the circle outline. Thus, the radius
- /// of said circle is the distance between `point_0` and `point_1`.
- ///
- /// The color at `point_0` is the color at `color_index_0` in the color
- /// table. The color on the circle outline is the color at `color_index_1`
- /// in the [`ColorTable`]. If a sampled point is inside the circle, the
- /// color is interpolated based on the distance to the center and the
- /// radius. If the point is not in the circle itself, the color at
- /// `color_index_1` is always taken.
- RadialGradient {
- /// The center point of the mental circle.
- point_0: Point,
- /// The end point of the gradient.
- point_1: Point,
- /// The color at `point_0`.
- color_index_0: u32,
- /// The color at `point_1`.
- color_index_1: u32,
- },
-}
-
-impl Style {
- fn read(reader: &mut impl Read, header: &TvgHeader, kind: StyleKind) -> io::Result<Self> {
- match kind {
- StyleKind::FlatColored => Self::read_flat_colored(reader),
- StyleKind::LinearGradient => Self::read_linear_gradient(reader, header),
- StyleKind::RadialGradient => Self::read_radial_gradient(reader, header),
- }
- }
-
- fn read_flat_colored(reader: &mut impl Read) -> io::Result<Self> {
- Ok(Self::FlatColored {
- color_index: read_varuint(reader)?,
- })
- }
-
- fn read_linear_gradient(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> {
- Ok(Self::LinearGradient {
- point_0: Point::read(reader, header)?,
- point_1: Point::read(reader, header)?,
- color_index_0: read_varuint(reader)?,
- color_index_1: read_varuint(reader)?,
- })
- }
-
- fn read_radial_gradient(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> {
- Ok(Self::RadialGradient {
- point_0: Point::read(reader, header)?,
- point_1: Point::read(reader, header)?,
- color_index_0: read_varuint(reader)?,
- color_index_1: read_varuint(reader)?,
- })
- }
-}
-
-/// TinyVG files contain a sequence of draw commands that must be executed in
-/// the defined order to get the final result. Each draw command adds a new 2D
-/// primitive to the graphic.
-#[derive(Debug, Clone)]
-pub enum Command {
- /// If this command is read, the TinyVG file has ended.
- ///
- /// This command must have prim_style_kind to be set to 0, so the last byte
- /// of every TinyVG file is `0x00`. Every byte after this command is
- /// considered not part of the TinyVG data and can be used for other
- /// purposes like metadata or similar.
- EndOfDocument,
- /// Fills a polygon with N [`Point`]s.
- ///
- /// The number of points must be at least 3. Files that encode a lower
- /// value must be discarded as ”invalid” by a conforming implementation.
- ///
- /// The polygon specified in polygon must be drawn using the even-odd rule.
- /// That means that if for any point to be inside the polygon, a line to
- /// infinity must cross an odd number of polygon segments.
- FillPolygon {
- /// The style that is used to fill the polygon.
- fill_style: Style,
- /// The points of the polygon.
- polygon: Box<[Point]>,
- },
- /// Fills a list of [`Rectangle`]s.
- ///
- /// The rectangles must be drawn first to last, which is the order they
- /// appear in the file.
- FillRectangles {
- /// The style that is used to fill all rectangles.
- fill_style: Style,
- /// The list of rectangles to be filled.
- rectangles: Box<[Rectangle]>,
- },
- /// Fills a [`Path`].
- ///
- /// For the filling, all path segments are considered a polygon each (drawn
- /// with even-odd rule) that, when overlap, also perform the even odd rule.
- /// This allows the user to carve out parts of the path and create
- /// arbitrarily shaped surfaces.
- FillPath {
- /// The style that is used to fill the path.
- fill_style: Style,
- /// A [`Path`] with `segment_count` segments.
- path: Path,
- },
- /// Draws a set of [`Line`]s.
- ///
- /// Each line is `line_width` units wide, and at least a single display
- /// pixel. This means that line_width of 0 is still visible, even though
- /// only marginally. This allows very thin outlines.
- DrawLines {
- /// The style that is used to draw the all lines.
- line_style: Style,
- /// The width of the lines.
- line_width: f64,
- /// The list of lines.
- lines: Box<[Line]>,
- },
- /// Draws a polygon.
- ///
- /// Each line is `line_width` units wide. The lines are drawn between
- /// consecutive points as well as the first and the last point.
- DrawLineLoop {
- /// The style that is used to draw the all lines.
- line_style: Style,
- /// The width of the line.
- line_width: f64,
- /// The points of the polygon.
- points: Box<[Point]>,
- },
- /// Draws a list of consecutive lines.
- ///
- /// The lines are drawn between consecutive points, but contrary to
- /// [`DrawLineLoop`], the first and the last point are not connected.
- DrawLineStrip {
- /// The style that is used to draw the all rectangles.
- line_style: Style,
- /// The width of the line.
- line_width: f64,
- /// The points of the line strip.
- points: Box<[Point]>,
- },
- /// Draws a [`Path`].
- ///
- /// The outline of the path is `line_width` units wide.
- DrawLinePath {
- /// The style that is used to draw the path.
- line_style: Style,
- /// The width of the line.
- line_width: f64,
- /// A path with `segment_count` segments.
- path: Path,
- },
- /// Fills a polygon and draws an outline at the same time.
- ///
- /// This command is a combination of [`FillPolygon`] and [`DrawLineLoop`].
- /// It first performs a [`FillPolygon`] with the `fill_style`, then
- /// performs [`DrawLineLoop`] with `line_style` and `line_width`.
- ///
- /// The outline commands use a reduced number of elements. The maximum
- /// number of points is 64.
- OutlineFillPolygon {
- /// The style that is used to fill the polygon.
- fill_style: Style,
- /// The style that is used to draw the outline of the polygon.
- line_style: Style,
- /// The width of the line.
- line_width: f64,
- /// The set of points of this polygon.
- points: Box<[Point]>,
- },
- /// Fills and outlines a list of [`Rectangle`]s.
- ///
- /// For each rectangle, it is first filled, then its outline is drawn, then
- /// the next rectangle is drawn.
- ///
- /// The outline commands use a reduced number of elements, the maximum
- /// number of points is 64.
- OutlineFillRectangles {
- /// The style that is used to fill the rectangles.
- fill_style: Style,
- /// The style that is used to draw the outline of the rectangles.
- line_style: Style,
- /// The width of the line.
- line_width: f64,
- /// The list of rectangles to be drawn.
- rectangles: Box<[Rectangle]>,
- },
- /// Fills a path and draws an outline at the same time.
- ///
- /// This command is a combination of [`FillPath`] and [`DrawLinePath`]. It
- /// first performs a [`FillPath`] with the `fill_style`, then performs
- /// [`DrawLinePath`] with `line_style` and `line_width`.
- OutlineFillPath {
- /// The style that is used to fill the path.
- fill_style: Style,
- /// The style that is used to draw the outline of the path.
- line_style: Style,
- /// The width of the line.
- line_width: f64,
- /// The path that should be drawn.
- path: Path,
- },
-}
-
-/// The header is different for outline commands, so we use this as a helper
-struct OutlineHeader {
- count: u32,
- fill_style: Style,
- line_style: Style,
- line_width: f64,
-}
-
-impl OutlineHeader {
- fn read(
- reader: &mut impl Read,
- header: &TvgHeader,
- prim_style_kind: StyleKind,
- ) -> Result<Self, TvgError> {
- // the count and secondary style kind are stores in the same byte
- let byte = reader.read_u8()?;
- let count = (byte & 0b0011_1111) as u32 + 1;
- let sec_style_kind = StyleKind::try_from((byte & 0b1100_0000) >> 6)?;
-
- let fill_style = Style::read(reader, header, prim_style_kind)?;
- let line_style = Style::read(reader, header, sec_style_kind)?;
-
- let line_width = read_unit(reader, header)?;
-
- Ok(Self {
- count,
- fill_style,
- line_style,
- line_width,
- })
- }
-}
-
-impl Command {
- pub fn read(reader: &mut impl Read, header: &TvgHeader) -> Result<Self, TvgError> {
- // the command name and primary style kind are stores in the same byte
- let byte = reader.read_u8()?;
- let command = CommandName::try_from(byte & 0b0011_1111)?;
- let style_kind = StyleKind::try_from((byte & 0b1100_0000) >> 6)?;
-
- match command {
- CommandName::EndOfDocument => Self::end_of_document(style_kind),
- CommandName::FillPolygon => Self::read_fill_polygon(reader, header, style_kind),
- CommandName::FillRectangles => Self::read_fill_rectangles(reader, header, style_kind),
- CommandName::FillPath => Self::read_fill_path(reader, header, style_kind),
- CommandName::DrawLines => Self::read_draw_lines(reader, header, style_kind),
- CommandName::DrawLineLoop => Self::read_draw_line_loop(reader, header, style_kind),
- CommandName::DrawLineStrip => Self::read_draw_line_strip(reader, header, style_kind),
- CommandName::DrawLinePath => Self::read_draw_line_path(reader, header, style_kind),
- CommandName::OutlineFillPolygon => {
- Self::read_outline_fill_polygon(reader, header, style_kind)
- }
- CommandName::OutlineFillRectangles => {
- Self::read_outline_fill_rectangles(reader, header, style_kind)
- }
- CommandName::OutlineFillPath => {
- Self::read_outline_fill_path(reader, header, style_kind)
- }
- }
- }
-
- pub fn is_end_of_document(&self) -> bool {
- matches!(self, Self::EndOfDocument)
- }
-
- fn read_command_header(
- reader: &mut impl Read,
- header: &TvgHeader,
- style_kind: StyleKind,
- ) -> io::Result<(u32, Style)> {
- // every command adds one to the count
- let count = read_varuint(reader)? + 1;
- let style = Style::read(reader, header, style_kind)?;
-
- Ok((count, style))
- }
-
- fn end_of_document(style_kind: StyleKind) -> Result<Self, TvgError> {
- if style_kind != StyleKind::FlatColored {
- Err(TvgError::InvalidEndOfDocument(style_kind as u8))
- } else {
- Ok(Self::EndOfDocument)
- }
- }
-
- fn read_fill_polygon(
- reader: &mut impl Read,
- header: &TvgHeader,
- style_kind: StyleKind,
- ) -> Result<Self, TvgError> {
- let (point_count, fill_style) = Self::read_command_header(reader, header, style_kind)?;
- if point_count < 3 {
- yeet!(TvgError::InvalidPolygon(point_count));
- }
-
- let polygon = Point::read_multiple(reader, header, point_count)?;
-
- Ok(Self::FillPolygon {
- fill_style,
- polygon,
- })
- }
-
- fn read_fill_rectangles(
- reader: &mut impl Read,
- header: &TvgHeader,
- style_kind: StyleKind,
- ) -> Result<Self, TvgError> {
- let (rectangle_count, fill_style) = Self::read_command_header(reader, header, style_kind)?;
- let rectangles = Rectangle::read_multiple(reader, header, rectangle_count)?;
-
- Ok(Self::FillRectangles {
- fill_style,
- rectangles,
- })
- }
-
- fn read_fill_path(
- reader: &mut impl Read,
- header: &TvgHeader,
- style_kind: StyleKind,
- ) -> Result<Self, TvgError> {
- let (segment_count, fill_style) = Self::read_command_header(reader, header, style_kind)?;
- let path = Path::read(reader, header, segment_count)?;
-
- Ok(Self::FillPath { fill_style, path })
- }
-
- fn read_draw_lines(
- reader: &mut impl Read,
- header: &TvgHeader,
- style_kind: StyleKind,
- ) -> Result<Self, TvgError> {
- let (line_count, line_style) = Self::read_command_header(reader, header, style_kind)?;
- let line_width = read_unit(reader, header)?;
- let lines = Line::read_multiple(reader, header, line_count)?;
-
- Ok(Self::DrawLines {
- line_style,
- line_width,
- lines,
- })
- }
-
- fn read_draw_line_loop(
- reader: &mut impl Read,
- header: &TvgHeader,
- style_kind: StyleKind,
- ) -> Result<Self, TvgError> {
- let (point_count, line_style) = Self::read_command_header(reader, header, style_kind)?;
- let line_width = read_unit(reader, header)?;
- let points = Point::read_multiple(reader, header, point_count)?;
-
- Ok(Self::DrawLineLoop {
- line_style,
- line_width,
- points,
- })
- }
-
- fn read_draw_line_strip(
- reader: &mut impl Read,
- header: &TvgHeader,
- style_kind: StyleKind,
- ) -> Result<Self, TvgError> {
- let (point_count, line_style) = Self::read_command_header(reader, header, style_kind)?;
- let line_width = read_unit(reader, header)?;
- let points = Point::read_multiple(reader, header, point_count)?;
-
- Ok(Self::DrawLineStrip {
- line_style,
- line_width,
- points,
- })
- }
-
- fn read_draw_line_path(
- reader: &mut impl Read,
- header: &TvgHeader,
- style_kind: StyleKind,
- ) -> Result<Self, TvgError> {
- let (segment_count, line_style) = Self::read_command_header(reader, header, style_kind)?;
- let line_width = read_unit(reader, header)?;
- let path = Path::read(reader, header, segment_count)?;
-
- Ok(Self::DrawLinePath {
- line_style,
- line_width,
- path,
- })
- }
-
- fn read_outline_fill_polygon(
- reader: &mut impl Read,
- header: &TvgHeader,
- style_kind: StyleKind,
- ) -> Result<Self, TvgError> {
- let OutlineHeader {
- count: segment_count,
- fill_style,
- line_style,
- line_width,
- } = OutlineHeader::read(reader, header, style_kind)?;
-
- let points = Point::read_multiple(reader, header, segment_count)?;
-
- Ok(Self::OutlineFillPolygon {
- fill_style,
- line_style,
- line_width,
- points,
- })
- }
-
- fn read_outline_fill_rectangles(
- reader: &mut impl Read,
- header: &TvgHeader,
- style_kind: StyleKind,
- ) -> Result<Self, TvgError> {
- let OutlineHeader {
- count: rect_count,
- fill_style,
- line_style,
- line_width,
- } = OutlineHeader::read(reader, header, style_kind)?;
-
- let rectangles = Rectangle::read_multiple(reader, header, rect_count)?;
-
- Ok(Self::OutlineFillRectangles {
- fill_style,
- line_style,
- line_width,
- rectangles,
- })
- }
-
- fn read_outline_fill_path(
- reader: &mut impl Read,
- header: &TvgHeader,
- style_kind: StyleKind,
- ) -> Result<Self, TvgError> {
- let OutlineHeader {
- count: segment_count,
- fill_style,
- line_style,
- line_width,
- } = OutlineHeader::read(reader, header, style_kind)?;
-
- let path = Path::read(reader, header, segment_count)?;
-
- Ok(Self::OutlineFillPath {
- fill_style,
- line_style,
- line_width,
- path,
- })
- }
-}
diff --git a/alligator_tvg/src/header.rs b/alligator_tvg/src/header.rs
deleted file mode 100644
index b3be494..0000000
--- a/alligator_tvg/src/header.rs
+++ /dev/null
@@ -1,149 +0,0 @@
-use std::io::{self, Read};
-
-use byteorder::{LittleEndian, ReadBytesExt};
-use num_enum::TryFromPrimitive;
-use raise::yeet;
-
-use crate::colors::ColorEncoding;
-use crate::read_varuint;
-use crate::TvgError;
-
-const MAGIC: [u8; 2] = [0x72, 0x56];
-pub const SUPPORTED_VERSION: u8 = 1;
-
-/// The coordinate range defines how many bits a Unit value uses.
-#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)]
-#[repr(u8)]
-pub enum CoordinateRange {
- /// Each [`Unit`] takes up 16 bits.
- #[default]
- Default = 0,
- /// Each [`Unit`] takes up 8 bits.
- Reduced = 1,
- /// Each [`Unit`] takes up 32 bits.
- Enhanced = 2,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)]
-#[repr(u8)]
-pub enum Scale {
- _1 = 0,
- _2 = 1,
- _4 = 2,
- _8 = 3,
- _16 = 4,
- _32 = 5,
- _64 = 6,
- _128 = 7,
- _256 = 8,
- _512 = 9,
- _1024 = 10,
- _2048 = 11,
- _4096 = 12,
- _8192 = 13,
- _16384 = 14,
- _32768 = 15,
-}
-
-/// Each TVG file starts with a header defining some global values for the file
-/// like scale and image size. This is a representation of the header, but not
-/// necessarily an exact representation of the bits of a TVG header.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct TvgHeader {
- /// Must always be [0x72, 0x56]
- magic: [u8; 2],
- /// Must be 1. For future versions, this field might decide how the rest of
- /// the format looks like.
- version: u8,
- /// Defines the number of fraction bits in a Unit value.
- scale: Scale,
- /// Defines the type of color information that is used in the
- /// [`ColorTable`].
- color_encoding: ColorEncoding,
- /// Defines the number of total bits in a Unit value and thus the overall
- /// precision of the file.
- coordinate_range: CoordinateRange,
- /// Encodes the maximum width of the output file in *display units*.
- ///
- /// A value of 0 indicates that the image has the maximum possible width.
- width: u32,
- /// Encodes the maximum height of the output file in *display units*.
- ///
- /// A value of 0 indicates that the image has the maximum possible height.
- height: u32,
- /// The number of colors in the color table.
- color_count: u32,
-}
-
-impl TvgHeader {
- pub fn read<R: Read>(reader: &mut R) -> Result<Self, TvgError> {
- // magic number is used as a first line defense against invalid data
- let magic = [reader.read_u8()?, reader.read_u8()?];
- if magic != MAGIC {
- yeet!(TvgError::InvalidFile);
- }
-
- // the version of tvg being used
- let version = reader.read_u8()?;
- if version != SUPPORTED_VERSION {
- yeet!(TvgError::UnsupportedVersion(version))
- }
-
- // scale, color_encoding, and coordinate_range are stored in one byte
- let byte = reader.read_u8()?;
- let scale = Scale::try_from_primitive(byte & 0b0000_1111).expect("invalid scale");
- let color_encoding = ColorEncoding::try_from_primitive((byte & 0b0011_0000) >> 4)
- .expect("invalid color encoding");
- let coordinate_range = CoordinateRange::try_from_primitive((byte & 0b1100_0000) >> 6)
- .expect("invalid coordinate range");
-
- // width and height depend on the coordinate range
- let width = read_unsigned_unit(coordinate_range, reader)?;
- let height = read_unsigned_unit(coordinate_range, reader)?;
-
- let color_count = read_varuint(reader)?;
-
- Ok(Self {
- magic,
- version,
- scale,
- color_encoding,
- coordinate_range,
- width,
- height,
- color_count,
- })
- }
-
- /// Defines the number of total bits in a Unit value and thus the overall
- /// precision of the file.
- pub fn coordinate_range(&self) -> CoordinateRange {
- self.coordinate_range
- }
-
- /// Defines the type of color information that is used in the [`ColorTable`].
- pub fn color_encoding(&self) -> ColorEncoding {
- self.color_encoding
- }
-
- /// Defines the number of fraction bits in a Unit value.
- pub fn scale(&self) -> Scale {
- self.scale
- }
-
- /// The number of colors in the [`ColorTable`].
- pub fn color_count(&self) -> u32 {
- self.color_count
- }
-}
-
-fn read_unsigned_unit<R: Read>(
- coordinate_range: CoordinateRange,
- bytes: &mut R,
-) -> io::Result<u32> {
- Ok(match coordinate_range {
- CoordinateRange::Reduced => bytes.read_u8()? as u32,
- CoordinateRange::Default => bytes.read_u16::<LittleEndian>()? as u32,
- CoordinateRange::Enhanced => bytes.read_u32::<LittleEndian>()?,
- })
-}
diff --git a/alligator_tvg/src/lib.rs b/alligator_tvg/src/lib.rs
deleted file mode 100644
index 5cbe33c..0000000
--- a/alligator_tvg/src/lib.rs
+++ /dev/null
@@ -1,151 +0,0 @@
-use std::io::{self, Read};
-
-use byteorder::{LittleEndian, ReadBytesExt};
-use colors::{Color, ColorTable};
-use commands::Command;
-use header::{CoordinateRange, Scale, TvgHeader};
-use thiserror::Error;
-
-mod colors;
-mod commands;
-mod header;
-mod path;
-
-pub use colors::Rgba16;
-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> {
- 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(),
- })
- }
-}
-
-#[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 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)
-}
diff --git a/alligator_tvg/src/path.rs b/alligator_tvg/src/path.rs
deleted file mode 100644
index d2bf4fb..0000000
--- a/alligator_tvg/src/path.rs
+++ /dev/null
@@ -1,294 +0,0 @@
-use std::io::{self, Read};
-
-use byteorder::ReadBytesExt;
-use num_enum::TryFromPrimitive;
-
-use crate::{header::TvgHeader, read_unit, read_varuint, Decode, Point};
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)]
-#[repr(u8)]
-enum Sweep {
- Right = 0,
- Left = 1,
-}
-
-/// An instruction to move a hypothetical "pen".
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)]
-#[repr(u8)]
-enum InstructionKind {
- /// A straight line is drawn from the current point to a new point.
- Line = 0,
- /// A straight horizontal line is drawn from the current point to a new x
- /// coordinate.
- HorizontalLine = 1,
- /// A straight vertical line is drawn from the current point to a new y
- /// coordiante.
- VerticalLine = 2,
- /// A cubic Bézier curve is drawn from the current point to a new point.
- CubicBezier = 3,
- /// A circle segment is drawn from current point to a new point.
- ArcCircle = 4,
- /// An ellipse segment is drawn from current point to a new point.
- ArcEllipse = 5,
- /// The path is closed, and a straight line is drawn to the starting point.
- ClosePath = 6,
- /// A quadratic Bézier curve is drawn from the current point to a new point.
- QuadraticBezier = 7,
-}
-
-#[derive(Debug, Clone, Copy)]
-enum InstructionData {
- /// The line instruction draws a straight line to the position.
- Line {
- /// The end point of the line.
- position: Point,
- },
- /// The horizontal line instruction draws a straight horizontal line to a
- /// given x coordinate.
- HorizontalLine {
- /// The new x coordinate.
- x: f64,
- },
- /// The vertical line instruction draws a straight vertical line to a given
- /// y coordinate.
- VerticalLine {
- /// The new y coordinate.
- y: f64,
- },
- /// The cubic bezier instruction draws a Bézier curve with two control
- /// points.
- ///
- /// The curve is drawn between the current location and `point_1` with
- /// `control_0` being the first control point and `control_1` being the
- /// second one.
- CubicBezier {
- /// The first control point.
- control_0: Point,
- /// The second control point.
- control_1: Point,
- /// The end point of the Bézier curve.
- point_1: Point,
- },
- /// Draws a circle segment between the current and the target point.
- ///
- /// The `radius` field determines the radius of the circle. If the distance
- /// between the current point and `target` is larger than `radius`, the
- /// distance is used as the radius.
- ArcCircle {
- /// If `true`, the large portion of the circle segment is drawn.
- large_arc: bool,
- /// Determines if the circle segment is left- or right bending.
- sweep: Sweep,
- /// The radius of the circle.
- radius: f64,
- /// The end point of the circle segment.
- target: Point,
- },
- /// Draws an ellipse segment between the current and the target point.
- ///
- /// The `radius_x` and `radius_y` fields determine the both radii of the
- /// ellipse. If the distance between the current point and target is not
- /// enough to fit any ellipse segment between the two points, `radius_x`
- /// and `radius_y` are scaled uniformly so that it fits exactly.
- ArcEllipse {
- /// If `true`, the large portion of the ellipse segment is drawn.
- large_arc: bool,
- /// Determines if the ellipse segment is left- or right bending.
- sweep: Sweep,
- /// The radius of the ellipse segment in the horizontal direction.
- radius_x: f64,
- /// The radius of the ellipse segment in the vertical direction.
- radius_y: f64,
- /// The rotation of the ellipse in mathematical negative direction, in
- /// degrees.
- rotation: f64,
- /// The end point of the ellipse segment.
- target: Point,
- },
- /// A straight line is drawn to the start location of the current segment.
- /// This instruction doesn’t have additional data encoded.
- ClosePath,
- /// The quadratic bezier instruction draws a Bézier curve with a single
- /// control point.
- ///
- /// The curve is drawn between the current location and `point_1` with
- /// control being the control point.
- QuadraticBezier {
- /// The control point.
- control: Point,
- /// The end point of the Bézier curve.
- target: Point,
- },
-}
-
-impl InstructionData {
- fn read(reader: &mut impl Read, header: &TvgHeader, kind: InstructionKind) -> io::Result<Self> {
- match kind {
- InstructionKind::Line => Self::read_line(reader, header),
- InstructionKind::HorizontalLine => Self::read_horizontal_line(reader, header),
- InstructionKind::VerticalLine => Self::read_vertical_line(reader, header),
- InstructionKind::CubicBezier => Self::read_cubic_bezier(reader, header),
- InstructionKind::ArcCircle => Self::read_arc_circle(reader, header),
- InstructionKind::ArcEllipse => Self::read_arc_ellipse(reader, header),
- InstructionKind::ClosePath => Ok(Self::ClosePath),
- InstructionKind::QuadraticBezier => Self::read_quadratic_bezier(reader, header),
- }
- }
-
- fn read_line(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> {
- Ok(Self::Line {
- position: Point::read(reader, header)?,
- })
- }
-
- fn read_horizontal_line(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> {
- Ok(Self::HorizontalLine {
- x: read_unit(reader, header)?,
- })
- }
-
- fn read_vertical_line(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> {
- Ok(Self::VerticalLine {
- y: read_unit(reader, header)?,
- })
- }
-
- fn read_cubic_bezier(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> {
- Ok(Self::CubicBezier {
- control_0: Point::read(reader, header)?,
- control_1: Point::read(reader, header)?,
- point_1: Point::read(reader, header)?,
- })
- }
-
- fn read_arc_header(reader: &mut impl Read, header: &TvgHeader) -> io::Result<(bool, Sweep)> {
- // large_arc and sweep are stored in the same byte
- let byte = reader.read_u8()?;
- let large_arc = (byte & 1) != 0;
- let sweep = match byte & 2 {
- 0 => Sweep::Left,
- _ => Sweep::Right,
- };
-
- Ok((large_arc, sweep))
- }
-
- fn read_arc_circle(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> {
- let (large_arc, sweep) = Self::read_arc_header(reader, header)?;
- let radius = read_unit(reader, header)?;
- let target = Point::read(reader, header)?;
-
- Ok(Self::ArcCircle {
- large_arc,
- sweep,
- radius,
- target,
- })
- }
-
- fn read_arc_ellipse(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> {
- let (large_arc, sweep) = Self::read_arc_header(reader, header)?;
- let radius_x = read_unit(reader, header)?;
- let radius_y = read_unit(reader, header)?;
- let rotation = read_unit(reader, header)?;
- let target = Point::read(reader, header)?;
-
- Ok(Self::ArcEllipse {
- large_arc,
- sweep,
- radius_x,
- radius_y,
- rotation,
- target,
- })
- }
-
- fn read_quadratic_bezier(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> {
- Ok(Self::QuadraticBezier {
- control: Point::read(reader, header)?,
- target: Point::read(reader, header)?,
- })
- }
-}
-
-#[derive(Debug, Clone)]
-struct Instruction {
- /// The width of the line the "pen" makes, if it makes one at all.
- line_width: Option<f64>,
- /// The arguments to the instruction.
- data: InstructionData,
-}
-
-impl Instruction {
- fn read(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> {
- let byte = reader.read_u8()?;
- let instruction_kind =
- InstructionKind::try_from_primitive(byte & 0b0000_0111).expect("invalid instruction");
- let has_line_width = (byte & 0b0001_0000) != 0;
-
- let line_width = has_line_width
- .then(|| read_unit(reader, header))
- .transpose()?;
- let data = InstructionData::read(reader, header, instruction_kind)?;
-
- Ok(Self { line_width, data })
- }
-}
-
-#[derive(Debug, Clone)]
-struct Segment {
- /// The starting point of the segment.
- start: Point,
- /// The list of instructions for tha segment.
- instructions: Box<[Instruction]>,
-}
-
-impl Segment {
- fn read(reader: &mut impl Read, header: &TvgHeader, segment_length: u32) -> io::Result<Self> {
- let start = Point::read(reader, header)?;
-
- let mut instructions = Vec::with_capacity(segment_length as usize);
- for _ in 0..segment_length {
- instructions.push(Instruction::read(reader, header)?)
- }
-
- Ok(Segment {
- start,
- instructions: instructions.into_boxed_slice(),
- })
- }
-}
-
-/// Paths describe instructions to create complex 2D graphics.
-///
-/// Each path segment generates a shape by moving a ”pen” around. The path this
-/// ”pen” takes is the outline of our segment. Each segment, the ”pen” starts
-/// at a defined position and is moved by instructions. Each instruction will
-/// leave the ”pen” at a new position. The line drawn by our ”pen” is the
-/// outline of the shape.
-#[derive(Debug, Clone)]
-pub struct Path {
- segments: Box<[Segment]>,
-}
-
-impl Path {
- pub(crate) fn read(
- reader: &mut impl Read,
- header: &TvgHeader,
- segment_count: u32,
- ) -> io::Result<Self> {
- let mut segment_lengths = Vec::with_capacity(segment_count as usize);
- for _ in 0..segment_count {
- segment_lengths.push(read_varuint(reader)? + 1);
- }
-
- let mut segments = Vec::with_capacity(segment_count as usize);
- for segment_length in segment_lengths {
- segments.push(Segment::read(reader, header, segment_length)?);
- }
-
- Ok(Self {
- segments: segments.into_boxed_slice(),
- })
- }
-}