From a49714e0ba3b1f0742b4a491399f85ee8713411a Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 6 Jul 2020 11:01:23 -0700 Subject: [PATCH] Flesh out the `web_canvas` crate a bit more --- canvas/src/lib.rs | 51 +++++++++- demo/common/src/lib.rs | 4 +- renderer/src/gpu/renderer.rs | 3 +- web_canvas/Cargo.toml | 12 ++- web_canvas/src/lib.rs | 180 ++++++++++++++++++++++++----------- 5 files changed, 192 insertions(+), 58 deletions(-) diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index 2cf4d091..897b96f1 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -89,6 +89,12 @@ impl Canvas { Canvas { scene } } + /// Returns the inner scene. + #[inline] + pub fn scene(&self) -> &Scene { + &self.scene + } + /// Returns the inner scene, replacing it with a blank scene. #[inline] pub fn take_scene(&mut self) -> Scene { @@ -98,6 +104,7 @@ impl Canvas { mem::replace(&mut self.scene, new_scene) } + /// Destroys this canvas and returns the inner scene. #[inline] pub fn into_scene(self) -> Scene { self.scene @@ -122,6 +129,12 @@ impl Canvas { pub fn size(&self) -> Vector2I { self.scene.view_box().size().ceil().to_i32() } + + pub fn set_size(&mut self, new_size: Vector2I) { + let new_view_box = RectI::new(Vector2I::default(), new_size).to_f32(); + self.scene.set_bounds(new_view_box); + self.scene.set_view_box(new_view_box); + } } pub struct CanvasRenderingContext2D { @@ -150,6 +163,13 @@ impl CanvasRenderingContext2D { self.canvas } + // Extensions + + /// Clears the current canvas. + pub fn clear(&mut self) { + drop(self.canvas.take_scene()) + } + // Drawing rectangles #[inline] @@ -184,26 +204,51 @@ impl CanvasRenderingContext2D { // Line styles + #[inline] + pub fn line_width(&self) -> f32 { + self.current_state.line_width + } + #[inline] pub fn set_line_width(&mut self, new_line_width: f32) { self.current_state.line_width = new_line_width } + #[inline] + pub fn line_cap(&self) -> LineCap { + self.current_state.line_cap + } + #[inline] pub fn set_line_cap(&mut self, new_line_cap: LineCap) { self.current_state.line_cap = new_line_cap } + #[inline] + pub fn line_join(&self) -> LineJoin { + self.current_state.line_join + } + #[inline] pub fn set_line_join(&mut self, new_line_join: LineJoin) { self.current_state.line_join = new_line_join } + #[inline] + pub fn miter_limit(&self) -> f32 { + self.current_state.miter_limit + } + #[inline] pub fn set_miter_limit(&mut self, new_miter_limit: f32) { self.current_state.miter_limit = new_miter_limit } + #[inline] + pub fn line_dash(&mut self) -> &[f32] { + &self.current_state.line_dash + } + #[inline] pub fn set_line_dash(&mut self, mut new_line_dash: Vec) { // Duplicate and concatenate if an odd number of dashes are present. @@ -216,6 +261,11 @@ impl CanvasRenderingContext2D { self.current_state.line_dash = new_line_dash } + #[inline] + pub fn line_dash_offset(&self) -> f32 { + self.current_state.line_dash_offset + } + #[inline] pub fn set_line_dash_offset(&mut self, new_line_dash_offset: f32) { self.current_state.line_dash_offset = new_line_dash_offset @@ -671,7 +721,6 @@ impl Path2D { #[inline] pub fn move_to(&mut self, to: Vector2F) { - // TODO(pcwalton): Cull degenerate contours. self.flush_current_contour(); self.current_contour.push_endpoint(to); } diff --git a/demo/common/src/lib.rs b/demo/common/src/lib.rs index baa3d0d2..ed385ea8 100644 --- a/demo/common/src/lib.rs +++ b/demo/common/src/lib.rs @@ -292,7 +292,9 @@ impl DemoApp where W: Window { let viewport = self.window.viewport(self.ui_model.mode.view(0)); self.scene_proxy.set_view_box(RectF::new(Vector2F::zero(), viewport.size().to_f32())); - self.renderer.set_main_framebuffer_size(self.window_size.device_size()); + self.renderer.options_mut().dest = + DestFramebuffer::full_window(self.window_size.device_size()); + self.renderer.dest_framebuffer_size_changed(); self.dirty = true; } Event::MouseDown(new_position) => { diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 91fe4edf..d3ed432b 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -489,7 +489,8 @@ impl Renderer where D: Device { } #[inline] - pub fn set_main_framebuffer_size(&mut self, new_framebuffer_size: Vector2I) { + pub fn dest_framebuffer_size_changed(&mut self) { + let new_framebuffer_size = self.core.main_viewport().size(); if let Some(ref mut debug_ui_presenter) = self.debug_ui_presenter { debug_ui_presenter.ui_presenter.set_framebuffer_size(new_framebuffer_size); } diff --git a/web_canvas/Cargo.toml b/web_canvas/Cargo.toml index c5898ee9..36ce35e5 100644 --- a/web_canvas/Cargo.toml +++ b/web_canvas/Cargo.toml @@ -47,4 +47,14 @@ wasm-bindgen-test = "0.3" [profile.release] # Tell `rustc` to optimize for small code size. -opt-level = "s" +# opt-level = "s" +debug = 1 + +[package.metadata.wasm-pack.profile.profiling] +wasm-opt = false +dwarf-debug-info = true + +[package.metadata.wasm-pack.profile.profiling.wasm-bindgen] +debug-js-glue = false +demangle-name-section = true +dwarf-debug-info = true diff --git a/web_canvas/src/lib.rs b/web_canvas/src/lib.rs index 9fbe322d..e2b908a6 100644 --- a/web_canvas/src/lib.rs +++ b/web_canvas/src/lib.rs @@ -9,32 +9,38 @@ // except according to those terms. use css_color_parser::Color; -use pathfinder_canvas::{Canvas, CanvasFontContext, CanvasRenderingContext2D, FillRule, Path2D}; -use pathfinder_color::{ColorF, ColorU}; +use pathfinder_canvas::{Canvas, CanvasFontContext, CanvasRenderingContext2D, FillRule, FillStyle}; +use pathfinder_canvas::{LineCap, Path2D}; +use pathfinder_color::ColorU; use pathfinder_geometry::rect::RectF; use pathfinder_geometry::transform2d::Transform2F; -use pathfinder_geometry::vector::{Vector2I, vec2f, vec2i}; +use pathfinder_geometry::vector::{vec2f, vec2i}; use pathfinder_renderer::concurrent::executor::SequentialExecutor; use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererMode, RendererOptions}; use pathfinder_renderer::gpu::renderer::Renderer; use pathfinder_renderer::options::BuildOptions; use pathfinder_resources::embedded::EmbeddedResourceLoader; use pathfinder_webgl::WebGlDevice; -use std::mem; use std::str::FromStr; +use std::sync::Arc; use wasm_bindgen::JsCast; use wasm_bindgen::prelude::*; -use web_sys::{self, console, HtmlCanvasElement, WebGl2RenderingContext}; +use web_sys::{self, HtmlCanvasElement, WebGl2RenderingContext}; #[wasm_bindgen] pub struct PFCanvasRenderingContext2D { html_canvas: HtmlCanvasElement, context: CanvasRenderingContext2D, - resource_loader: EmbeddedResourceLoader, - renderer: Option>, + renderer: Renderer, default_path: Path2D, - // FIXME(pcwalton): Remove this once renderers are resizable. - prev_framebuffer_size: Vector2I, + current_state: WebCanvasState, + saved_states: Vec, +} + +#[derive(Clone)] +struct WebCanvasState { + fill_style_string: Arc, + stroke_style_string: Arc, } #[wasm_bindgen(js_name = "createContext")] @@ -55,7 +61,7 @@ pub fn create_context(html_canvas: HtmlCanvasElement) -> PFCanvasRenderingContex let mode = RendererMode::default_for_device(&pathfinder_device); let options = RendererOptions { dest: DestFramebuffer::full_window(framebuffer_size), - background_color: Some(ColorF::white()), + background_color: None, ..RendererOptions::default() }; let resource_loader = EmbeddedResourceLoader::new(); @@ -68,41 +74,37 @@ pub fn create_context(html_canvas: HtmlCanvasElement) -> PFCanvasRenderingContex PFCanvasRenderingContext2D { html_canvas, context, - resource_loader, - renderer: Some(renderer), + renderer, default_path: Path2D::new(), - prev_framebuffer_size: framebuffer_size, + current_state: WebCanvasState { + fill_style_string: Arc::new("black".to_owned()), + stroke_style_string: Arc::new("black".to_owned()), + }, + saved_states: vec![], } } #[wasm_bindgen] impl PFCanvasRenderingContext2D { - pub fn flush(&mut self) { + #[wasm_bindgen(js_name = "pfFlush")] + pub fn pf_flush(&mut self) { // Update framebuffer size. let framebuffer_size = vec2i(self.html_canvas.width() as i32, self.html_canvas.height() as i32); - if self.prev_framebuffer_size != framebuffer_size { - // Recreate the Pathfinder renderer. - // - // FIXME(pcwalton): This shouldn't be necessary! - let pathfinder_device = self.renderer.take().unwrap().destroy(); - let mode = RendererMode::default_for_device(&pathfinder_device); - let options = RendererOptions { - dest: DestFramebuffer::full_window(framebuffer_size), - background_color: Some(ColorF::white()), - ..RendererOptions::default() - }; - self.renderer = Some(Renderer::new(pathfinder_device, - &self.resource_loader, - mode, - options)); - self.prev_framebuffer_size = framebuffer_size; - } + self.renderer.options_mut().dest = DestFramebuffer::full_window(framebuffer_size); + self.renderer.options_mut().background_color = None; + self.renderer.dest_framebuffer_size_changed(); - let mut scene = self.context.canvas_mut().take_scene(); - scene.build_and_render(self.renderer.as_mut().unwrap(), - BuildOptions::default(), - SequentialExecutor); + // TODO(pcwalton): This is inefficient! + let mut scene = (*self.context.canvas_mut().scene()).clone(); + scene.build_and_render(&mut self.renderer, BuildOptions::default(), SequentialExecutor); + + self.context.canvas_mut().set_size(framebuffer_size); + } + + #[wasm_bindgen(js_name = "pfClear")] + pub fn pf_clear(&mut self) { + self.context.clear(); } #[wasm_bindgen(getter)] @@ -110,26 +112,11 @@ impl PFCanvasRenderingContext2D { self.html_canvas.clone() } - #[wasm_bindgen(js_name = "fillStyle")] - #[wasm_bindgen(setter)] - pub fn set_fill_style(&mut self, new_style: &str) { - let css_color = match Color::from_str(new_style) { - Err(_) => return, - Ok(css_color) => css_color, - }; - let color = ColorU::new(css_color.r, - css_color.g, - css_color.b, - (css_color.a * 255.0).round() as u8); - self.context.set_fill_style(color); - } + // Drawing rectangles - pub fn save(&mut self) { - self.context.save(); - } - - pub fn restore(&mut self) { - self.context.restore(); + #[wasm_bindgen(js_name = "clearRect")] + pub fn clear_rect(&mut self, x: f32, y: f32, width: f32, height: f32) { + self.context.clear_rect(RectF::new(vec2f(x, y), vec2f(width, height))); } #[wasm_bindgen(js_name = "fillRect")] @@ -137,6 +124,67 @@ impl PFCanvasRenderingContext2D { self.context.fill_rect(RectF::new(vec2f(x, y), vec2f(width, height))); } + #[wasm_bindgen(js_name = "strokeRect")] + pub fn stroke_rect(&mut self, x: f32, y: f32, width: f32, height: f32) { + self.context.stroke_rect(RectF::new(vec2f(x, y), vec2f(width, height))); + } + + // TODO(pcwalton): Drawing text + + // Line styles + + #[wasm_bindgen(js_name = "lineWidth")] + #[wasm_bindgen(getter)] + pub fn line_width(&self) -> f32 { + self.context.line_width() + } + + #[wasm_bindgen(js_name = "lineWidth")] + #[wasm_bindgen(setter)] + pub fn set_line_width(&mut self, new_line_width: f32) { + self.context.set_line_width(new_line_width); + } + + #[wasm_bindgen(js_name = "lineCap")] + #[wasm_bindgen(getter)] + pub fn line_cap(&self) -> String { + match self.context.line_cap() { + LineCap::Butt => "butt".to_owned(), + LineCap::Round => "round".to_owned(), + LineCap::Square => "square".to_owned(), + } + } + + #[wasm_bindgen(js_name = "lineCap")] + #[wasm_bindgen(setter)] + pub fn set_line_cap(&mut self, new_line_cap: &str) { + if new_line_cap == "butt" { + self.context.set_line_cap(LineCap::Butt) + } else if new_line_cap == "round" { + self.context.set_line_cap(LineCap::Round) + } else if new_line_cap == "square" { + self.context.set_line_cap(LineCap::Square) + } + } + + #[wasm_bindgen(js_name = "fillStyle")] + #[wasm_bindgen(setter)] + pub fn set_fill_style(&mut self, new_style_string: &str) { + if let Some(new_style) = parse_fill_or_stroke_style(new_style_string) { + self.context.set_fill_style(new_style); + self.current_state.fill_style_string = Arc::new(new_style_string.to_owned()); + } + } + + #[wasm_bindgen(js_name = "strokeStyle")] + #[wasm_bindgen(setter)] + pub fn set_stroke_style(&mut self, new_style_string: &str) { + if let Some(new_style) = parse_fill_or_stroke_style(new_style_string) { + self.context.set_stroke_style(new_style); + self.current_state.stroke_style_string = Arc::new(new_style_string.to_owned()); + } + } + pub fn transform(&mut self, a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) { let new_transform = self.context.transform() * Transform2F::row_major(a, c, e, b, d, f); self.context.set_transform(&new_transform) @@ -193,4 +241,28 @@ impl PFCanvasRenderingContext2D { let path = self.default_path.clone(); self.context.stroke_path(path); } + + pub fn save(&mut self) { + self.context.save(); + self.saved_states.push(self.current_state.clone()); + } + + pub fn restore(&mut self) { + if let Some(saved_state) = self.saved_states.pop() { + self.current_state = saved_state; + } + self.context.restore(); + } +} + +fn parse_fill_or_stroke_style(string: &str) -> Option { + let css_color = match Color::from_str(string) { + Err(_) => return None, + Ok(css_color) => css_color, + }; + let color = ColorU::new(css_color.r, + css_color.g, + css_color.b, + (css_color.a * 255.0).round() as u8); + Some(FillStyle::Color(color)) }