use crate::{
path::{Command, Curve, PathContent, PathFlags},
shape::{LineCap, LineJoin, Transformer},
style::{GradientType, Style},
Hvif, Point, Transform,
};
trait CairoContextExt {
fn curve_to_point(&self, point_in: &Point, point_out: &Point, point: &Point);
}
impl CairoContextExt for cairo::Context {
fn curve_to_point(&self, point_in: &Point, point_out: &Point, point: &Point) {
self.curve_to(
point_in.x.to_f64(),
point_in.y.to_f64(),
point_out.x.to_f64(),
point_out.y.to_f64(),
point.x.to_f64(),
point.y.to_f64(),
);
}
}
fn transform_to_cairo(transform: &Option<Transform>) -> cairo::Matrix {
if let Some(transform) = transform {
cairo::Matrix::new(
transform[0].to_f64(),
transform[1].to_f64(),
transform[2].to_f64(),
transform[3].to_f64(),
transform[4].to_f64(),
transform[5].to_f64(),
)
} else {
cairo::Matrix::identity()
}
}
fn style_colour_to_cairo(ctx: &cairo::Context, style: &Style) {
match style {
Style::SolidColour(colour) => {
if colour.is_opaque() {
let (r, g, b) = colour.to_rgb();
let r = (r as f64) / 255.;
let g = (g as f64) / 255.;
let b = (b as f64) / 255.;
ctx.set_source_rgb(r, g, b);
} else {
let (r, g, b, a) = colour.to_rgba();
let r = (r as f64) / 255.;
let g = (g as f64) / 255.;
let b = (b as f64) / 255.;
let a = (a as f64) / 255.;
ctx.set_source_rgba(r, g, b, a);
}
}
Style::Gradient(ref g) => match g.type_ {
GradientType::Linear => {
let gradient = cairo::LinearGradient::new(-64., 0., 64., 0.);
for stop in g.stops.iter() {
let offset = (stop.offset as f64) / 255.;
let (r, g, b, a) = stop.colour.to_rgba();
let r = (r as f64) / 255.;
let g = (g as f64) / 255.;
let b = (b as f64) / 255.;
let a = (a as f64) / 255.;
gradient.add_color_stop_rgba(offset, r, g, b, a);
}
let mut matrix = transform_to_cairo(&g.transform);
matrix.invert();
gradient.set_matrix(matrix);
ctx.set_source(&gradient).unwrap();
}
GradientType::Circular => {
let gradient = cairo::RadialGradient::new(0., 0., 0., 0., 0., 64.);
for stop in g.stops.iter() {
let offset = (stop.offset as f64) / 255.;
let (r, g, b, a) = stop.colour.to_rgba();
let r = (r as f64) / 255.;
let g = (g as f64) / 255.;
let b = (b as f64) / 255.;
let a = (a as f64) / 255.;
gradient.add_color_stop_rgba(offset, r, g, b, a);
}
let mut matrix = transform_to_cairo(&g.transform);
matrix.invert();
gradient.set_matrix(matrix);
ctx.set_source(&gradient).unwrap();
}
type_ => todo!("Unimplemented gradient type: {:?}", type_),
},
}
}
pub fn render(hvif: Hvif, surface: &mut cairo::ImageSurface) -> Result<(), cairo::Error> {
surface.set_device_scale((surface.width() as f64) / 64., (surface.height() as f64) / 64.);
let ctx = cairo::Context::new(&surface)?;
for shape in hvif.shapes {
let style = &hvif.styles[shape.style as usize];
style_colour_to_cairo(&ctx, style);
let mut use_fill = true;
for transformer in shape.transformers {
match transformer {
Transformer::Stroke {
width,
line_join,
line_cap,
miter_limit,
} => {
ctx.set_line_width(width as f64);
ctx.set_line_join(match line_join {
LineJoin::MiterJoin => cairo::LineJoin::Miter,
LineJoin::MiterJoinRevert => cairo::LineJoin::Miter,
LineJoin::RoundJoin => cairo::LineJoin::Round,
LineJoin::BevelJoin => cairo::LineJoin::Bevel,
LineJoin::MiterJoinRound => cairo::LineJoin::Round,
});
ctx.set_line_cap(match line_cap {
LineCap::Butt => cairo::LineCap::Butt,
LineCap::Square => cairo::LineCap::Square,
LineCap::Round => cairo::LineCap::Round,
});
ctx.set_miter_limit(miter_limit as f64);
use_fill = false;
}
_ => {
ctx.set_line_width(0.);
ctx.set_miter_limit(0.);
}
}
}
let matrix = transform_to_cairo(&shape.transform);
ctx.set_matrix(matrix);
for &path in shape.paths.iter() {
let path = &hvif.paths[path as usize];
if let PathContent::Lines(ref points) = path.content {
for (i, p) in points.iter().enumerate() {
if i == 0 {
ctx.move_to(p.x.to_f64(), p.y.to_f64());
} else {
ctx.line_to(p.x.to_f64(), p.y.to_f64());
}
}
} else if let PathContent::Curves(ref curves) = path.content {
let mut iter = curves.iter();
let first;
let first_in;
let mut prev_out;
if let Some(curve) = iter.next() {
ctx.move_to(curve.point.x.to_f64(), curve.point.y.to_f64());
first = curve.point.clone();
first_in = curve.point_in.clone();
prev_out = curve.point_out.clone();
} else {
unreachable!();
}
for curve in iter {
ctx.curve_to_point(&prev_out, &curve.point_in, &curve.point);
prev_out = curve.point_out.clone();
}
ctx.curve_to_point(&prev_out, &first_in, &first);
} else if let PathContent::Commands(ref commands) = path.content {
let first;
let first_in;
let mut prev;
let mut last_point;
let mut iter = commands.iter();
if let Some(command) = iter.next() {
if let Command::Line(point) = command {
ctx.move_to(point.x.to_f64(), point.y.to_f64());
first = point;
first_in = point;
prev = first.clone();
last_point = point.clone();
} else if let Command::Curve(Curve {
point,
point_in,
point_out: _,
}) = command
{
ctx.move_to(point.x.to_f64(), point.y.to_f64());
first = point;
prev = first.clone();
last_point = point.clone();
first_in = point_in;
} else {
unreachable!();
}
} else {
unreachable!();
}
for command in iter {
match command {
Command::HLine(x) => {
ctx.line_to(x.to_f64(), last_point.y.to_f64());
last_point.x = x.clone();
prev = last_point.clone();
}
Command::VLine(y) => {
ctx.line_to(last_point.x.to_f64(), y.to_f64());
last_point.y = y.clone();
prev = last_point.clone();
}
Command::Line(point) => {
ctx.line_to(point.x.to_f64(), point.y.to_f64());
last_point = point.clone();
prev = last_point.clone();
}
Command::Curve(Curve {
ref point,
ref point_in,
ref point_out,
}) => {
ctx.curve_to_point(&prev, point_in, point);
last_point = point.clone();
prev = point_out.clone();
}
}
}
ctx.curve_to_point(&prev, &first_in, &first);
} else {
unreachable!();
}
if path.flags.contains(PathFlags::CLOSED) {
ctx.close_path();
}
}
if use_fill {
ctx.fill()?;
} else {
ctx.stroke()?;
}
}
Ok(())
}