diff options
| author | Micha White <botahamec@outlook.com> | 2023-02-13 00:24:07 -0500 |
|---|---|---|
| committer | Micha White <botahamec@outlook.com> | 2023-02-13 00:24:07 -0500 |
| commit | 861b467b95be55db3a42182b77dba944869bf49f (patch) | |
| tree | f58057d5ab9ad53601332981a31553b0ad05fe1b /tvg/src/commands.rs | |
| parent | c31d51487082c6cf243ecd10da71a15a78d41add (diff) | |
Rename the subdirectories
Diffstat (limited to 'tvg/src/commands.rs')
| -rw-r--r-- | tvg/src/commands.rs | 613 |
1 files changed, 613 insertions, 0 deletions
diff --git a/tvg/src/commands.rs b/tvg/src/commands.rs new file mode 100644 index 0000000..f316a53 --- /dev/null +++ b/tvg/src/commands.rs @@ -0,0 +1,613 @@ +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, + }) + } +} |
