-
-
Notifications
You must be signed in to change notification settings - Fork 307
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
275 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
use crate::draw::primitive::path::Options; | ||
use crate::draw::properties::LinSrgba; | ||
use crate::lyon::math::Point; | ||
use lyon::lyon_tessellation::{LineCap, LineJoin}; | ||
use lyon::path::{Event, FillRule, PathEvent}; | ||
use nannou_core::color::{Alpha, Srgb}; | ||
use nannou_core::geom::Rect; | ||
use svg::node::element::path::Position::Absolute; | ||
use svg::node::element::path::{Command, Data, Parameters}; | ||
use svg::node::element::{Group, Path}; | ||
use svg::{Document, Node}; | ||
|
||
pub fn svg_document(dims: Rect, elements: Group, background: Option<LinSrgba>) -> Document { | ||
let dims = dims.absolute(); | ||
let corner = dims.bottom_left(); // smallest x and y | ||
let size = dims.wh(); | ||
let y_offset = dims.h().copysign(dims.y()); | ||
|
||
let mut document = Document::new() | ||
.set("viewBox", (corner.x, corner.y, size.x, size.y)) | ||
.add(elements.set( | ||
"transform", | ||
format!("translate(0 {}) scale(1 -1)", y_offset), // in svg, +y is down; in nannou, +y is up | ||
)); | ||
|
||
if let Some(color) = background { | ||
document.assign( | ||
"style", | ||
format!("background-color: {};", convert_color(color)), | ||
); | ||
} | ||
|
||
document | ||
} | ||
|
||
pub fn render_path<I>(svg: &mut Group, events: I, color: LinSrgba, options: Options) | ||
where | ||
I: Iterator<Item = PathEvent>, | ||
{ | ||
let path_data = lyon_to_svg_path_data(events); | ||
render_path_data(svg, path_data, color, options); | ||
} | ||
|
||
pub fn render_path_data(svg: &mut Group, path_data: Data, color: LinSrgba, options: Options) { | ||
svg.append(path_options(color, options).set("d", path_data)); | ||
} | ||
|
||
pub fn lyon_to_svg_path_data<I>(events: I) -> Data | ||
where | ||
I: Iterator<Item = PathEvent>, | ||
{ | ||
let mut data = Vec::with_capacity(events.size_hint().0); | ||
|
||
// follows lyon::FromPolyline convention that the previous Line.to == current Line.from | ||
for event in events { | ||
match event { | ||
Event::Begin { at } => data.push(Command::Move(Absolute, param(at))), | ||
Event::Line { to, .. } => data.push(Command::Line(Absolute, param(to))), | ||
Event::Quadratic { ctrl, to, .. } => { | ||
data.push(Command::QuadraticCurve(Absolute, params(&[ctrl, to]))) | ||
} | ||
Event::Cubic { | ||
ctrl1, ctrl2, to, .. | ||
} => data.push(Command::CubicCurve(Absolute, params(&[ctrl1, ctrl2, to]))), | ||
Event::End { first, close, .. } => { | ||
if close { | ||
data.push(Command::Line(Absolute, param(first))); | ||
} | ||
data.push(Command::Close); | ||
} | ||
} | ||
} | ||
|
||
data.into() | ||
} | ||
|
||
fn path_options(color: LinSrgba, options: Options) -> Path { | ||
match options { | ||
Options::Fill(options) => Path::new() | ||
.set("stroke", "none") | ||
.set("fill", convert_color(color)) | ||
.set( | ||
"fill-rule", | ||
match options.fill_rule { | ||
FillRule::EvenOdd => "evenodd", | ||
FillRule::NonZero => "nonzero", | ||
}, | ||
), | ||
Options::Stroke(options) => { | ||
if options.start_cap != options.end_cap { | ||
unimplemented!(); | ||
} | ||
Path::new() | ||
.set("fill", "none") | ||
.set("stroke", convert_color(color)) | ||
.set( | ||
"stroke-linecap", | ||
match options.start_cap { | ||
LineCap::Butt => "butt", | ||
LineCap::Square => "round", | ||
LineCap::Round => "square", | ||
}, | ||
) | ||
.set( | ||
"stroke-line_join", | ||
match options.line_join { | ||
LineJoin::Miter => "miter", | ||
LineJoin::MiterClip => "miter-clip", | ||
LineJoin::Round => "round", | ||
LineJoin::Bevel => "bevel", | ||
}, | ||
) | ||
.set("stroke-width", options.line_width) | ||
.set("stroke-miterlimit", options.miter_limit) | ||
} | ||
} | ||
} | ||
|
||
fn convert_color(color: LinSrgba) -> String { | ||
let color: Alpha<Srgb<u8>, f32> = color.into_encoding().into_format(); | ||
|
||
format!( | ||
"rgba({}, {}, {}, {:.3})", | ||
color.red, color.green, color.blue, color.alpha | ||
) | ||
} | ||
|
||
fn param(pt: Point) -> Parameters { | ||
let pt: (f32, f32) = pt.into(); | ||
pt.into() | ||
} | ||
|
||
fn params(points: &[Point]) -> Parameters { | ||
let mut params = Vec::with_capacity(points.len() * 2); | ||
for pt in points { | ||
params.push(pt.x); | ||
params.push(pt.y); | ||
} | ||
params.into() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
mod encode; | ||
|
||
use crate::draw::mesh::vertex::TexCoords; | ||
use crate::draw::primitive::path::Options; | ||
use crate::draw::properties::LinSrgba; | ||
use crate::draw::renderer::{PrimitiveRenderer, RenderContext, RenderPrimitive}; | ||
use crate::draw::{self, DrawCommand}; | ||
use crate::glam::{Mat4, Vec2}; | ||
use crate::Draw; | ||
use nannou_core::geom::Rect; | ||
use std::fs::File; | ||
use std::io::{self, Write}; | ||
use std::path::Path; | ||
use svg::node::element::Group; | ||
use svg::Document; | ||
|
||
pub fn render_and_save(dims: Rect, draw: &Draw, path: impl AsRef<Path>) { | ||
let document = render(dims, draw); | ||
write_file(path, &document).unwrap(); | ||
} | ||
|
||
fn write_file(path: impl AsRef<Path>, document: &Document) -> io::Result<()> { | ||
let mut file = File::create(path)?; | ||
file.write_all(&document.to_string().into_bytes())?; | ||
file.sync_all() | ||
} | ||
|
||
pub fn render(dims: Rect, draw: &Draw) -> Document { | ||
let draw_commands = draw.drain_commands(); | ||
let draw_state = draw.state.borrow(); | ||
let intermediary_state = draw_state.intermediary_state.borrow(); | ||
|
||
let mut svg = Group::new(); | ||
let mut curr_ctxt = Default::default(); | ||
|
||
for command in draw_commands { | ||
match command { | ||
DrawCommand::Context(new_context) => { | ||
if new_context != Default::default() { | ||
unimplemented!(); | ||
} | ||
curr_ctxt = new_context; | ||
} | ||
DrawCommand::Primitive(primitive) => { | ||
let ctxt = RenderContext { | ||
path_event_buffer: &intermediary_state.path_event_buffer, | ||
path_points_colored_buffer: &intermediary_state.path_points_colored_buffer, | ||
path_points_textured_buffer: &intermediary_state.path_points_textured_buffer, | ||
text_buffer: &intermediary_state.text_buffer, | ||
theme: &draw_state.theme, | ||
}; | ||
|
||
let renderer = SvgPrimitiveRenderer { | ||
transform: &curr_ctxt.transform, | ||
theme: &draw_state.theme, | ||
svg: &mut svg, | ||
}; | ||
|
||
let _ = primitive.render_primitive(ctxt, renderer); | ||
} | ||
} | ||
} | ||
|
||
encode::svg_document(dims, svg, draw_state.background_color) | ||
} | ||
|
||
struct SvgPrimitiveRenderer<'a> { | ||
transform: &'a Mat4, | ||
theme: &'a draw::Theme, | ||
svg: &'a mut Group, | ||
} | ||
|
||
impl<'a> PrimitiveRenderer for SvgPrimitiveRenderer<'a> { | ||
fn path_flat_color( | ||
&mut self, | ||
local_transform: Mat4, | ||
events: impl Iterator<Item = lyon::path::PathEvent>, | ||
color: Option<LinSrgba>, | ||
theme_primitive: draw::theme::Primitive, | ||
options: Options, | ||
) { | ||
let transform = *self.transform * local_transform; | ||
if transform != Mat4::IDENTITY { | ||
unimplemented!(); | ||
} | ||
|
||
let color = self.theme.resolve_color(color, theme_primitive, &options); | ||
encode::render_path(self.svg, events, color, options); | ||
} | ||
|
||
fn path_colored_points( | ||
&mut self, | ||
_local_transform: Mat4, | ||
_points_colored: impl Iterator<Item = (Vec2, LinSrgba)>, | ||
_close: bool, | ||
_options: Options, | ||
) { | ||
unimplemented!(); | ||
} | ||
|
||
fn path_textured_points( | ||
&mut self, | ||
_local_transform: Mat4, | ||
_points_textured: impl Iterator<Item = (Vec2, TexCoords)>, | ||
_close: bool, | ||
_options: Options, | ||
) { | ||
unimplemented!(); | ||
} | ||
|
||
fn mesh( | ||
&mut self, | ||
_local_transform: Mat4, | ||
_vertex_range: std::ops::Range<usize>, | ||
_index_range: std::ops::Range<usize>, | ||
_fill_color: Option<LinSrgba>, | ||
) { | ||
unimplemented!(); | ||
} | ||
|
||
fn text( | ||
&mut self, | ||
local_transform: Mat4, | ||
_text: crate::text::Text, | ||
_color: LinSrgba, | ||
_glyph_colors: Vec<LinSrgba>, | ||
) { | ||
let _transform = *self.transform * local_transform; | ||
// TODO: render each glyph as a path_flat_color | ||
unimplemented!(); | ||
} | ||
} |