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(),
})
}
}
|