diff options
Diffstat (limited to 'tvg/src/commands.rs')
| -rw-r--r-- | tvg/src/commands.rs | 756 |
1 files changed, 0 insertions, 756 deletions
diff --git a/tvg/src/commands.rs b/tvg/src/commands.rs deleted file mode 100644 index c21099b..0000000 --- a/tvg/src/commands.rs +++ /dev/null @@ -1,756 +0,0 @@ -use std::io::{self, Read}; -use std::ops::{Add, Mul, Sub}; - -use byteorder::ReadBytesExt; -use raise::yeet; - -use crate::{ - colors::{self, Color, ColorTable}, - header::TvgHeader, - path::Path, - read_unit, read_varuint, Decode, TvgError, -}; - -/// A X and Y coordinate pair. -#[derive(Debug, Clone, Copy)] -pub struct Vector { - /// Horizontal distance of the point to the origin. - pub x: f64, - /// Vertical distance of the point to the origin. - pub y: f64, -} - -impl Vector { - pub fn magnitude(&self) -> f64 { - (self.x * self.x + self.y * self.y).sqrt() - } - - pub fn scale(&self, scale: f64) -> Vector { - Vector { - x: self.x * scale, - y: self.y * scale, - } - } -} - -impl Mul for Vector { - type Output = f64; - - fn mul(self, rhs: Self) -> Self::Output { - self.x * rhs.x + self.y * rhs.y - } -} - -/// A X and Y coordinate pair. -#[derive(Debug, Clone, Copy)] -pub struct Point { - /// Horizontal distance of the point to the origin. - pub x: f64, - /// Vertical distance of the point to the origin. - pub y: f64, -} - -impl Sub for Point { - type Output = Vector; - - fn sub(self, rhs: Self) -> Self::Output { - Vector { - x: self.x - rhs.x, - y: self.y - rhs.y, - } - } -} - -impl Add<Vector> for Point { - type Output = Self; - - fn add(self, vector: Vector) -> Self::Output { - Self { - x: self.x + vector.x, - y: self.y + vector.y, - } - } -} - -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)?, - }) - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Rectangle { - /// Horizontal distance of the left side to the origin. - pub x: f64, - /// Vertical distance of the upper side to the origin. - pub y: f64, - /// Horizontal extent of the rectangle. - pub width: f64, - /// Vertical extent of the rectangle. - pub height: f64, -} - -impl Decode for Rectangle { - fn read(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> { - let x = 5; - 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)?, - }) - } - - fn linear_get_color_at<C: Color + Clone>( - &self, - color_table: &ColorTable<C>, - point: Point, - ) -> Option<C> { - let Self::LinearGradient { - point_0, - point_1, - color_index_0, - color_index_1 } = self else { panic!() }; - - // TODO remove these unwraps - let color_0 = color_table.get(*color_index_0 as usize)?; - let color_1 = color_table.get(*color_index_1 as usize)?; - - let direction = *point_1 - *point_0; - let delta = point - *point_0; - - if direction * delta <= 0.0 { - return Some(color_0.clone()); - } else if direction * (point - *point_1) >= 0.0 { - return Some(color_1.clone()); - } - - let gradient_length = direction.magnitude(); - let proj = (direction * delta) / (direction * direction); - let gradient_position = direction.scale(proj).magnitude(); - let f = gradient_position / gradient_length; - - Some(colors::lerp(color_0, color_1, f)) - } - - fn radial_get_color_at<C: Color>( - &self, - color_table: &ColorTable<C>, - point: Point, - ) -> Option<C> { - let Self::RadialGradient { - point_0, - point_1, - color_index_0, - color_index_1 } = self else { panic!() }; - - // TODO remove these unwraps - let color_0 = color_table.get(*color_index_0 as usize)?; - let color_1 = color_table.get(*color_index_1 as usize)?; - - let distance_max = (*point_1 - *point_0).magnitude(); - let distance_is = (point - *point_0).magnitude(); - let f = distance_is / distance_max; - - Some(colors::lerp(color_0, color_1, f)) - } - - pub fn get_color_at<C: Color + Clone>( - &self, - color_table: &ColorTable<C>, - point: Point, - ) -> Option<C> { - match self { - Self::FlatColored { color_index } => color_table.get(*color_index as usize).cloned(), - Self::LinearGradient { .. } => self.linear_get_color_at(color_table, point), - Self::RadialGradient { .. } => self.radial_get_color_at(color_table, point), - } - } -} - -/// 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, - }) - } -} |
