summaryrefslogtreecommitdiff
path: root/tvg/src/commands.rs
diff options
context:
space:
mode:
authorMicha White <botahamec@outlook.com>2023-02-13 00:24:07 -0500
committerMicha White <botahamec@outlook.com>2023-02-13 00:24:07 -0500
commit861b467b95be55db3a42182b77dba944869bf49f (patch)
treef58057d5ab9ad53601332981a31553b0ad05fe1b /tvg/src/commands.rs
parentc31d51487082c6cf243ecd10da71a15a78d41add (diff)
Rename the subdirectories
Diffstat (limited to 'tvg/src/commands.rs')
-rw-r--r--tvg/src/commands.rs613
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,
+ })
+ }
+}