From e54e0e4760d4c64e2b55421999587262eedf8f3c Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sat, 4 Jul 2020 09:33:22 -0700 Subject: [PATCH 1/2] Start a new `web_canvas` crate, for usage in the browser as a `` replacement --- Cargo.lock | 70 +++++--- Cargo.toml | 1 + canvas/src/lib.rs | 14 ++ canvas/src/text.rs | 23 --- examples/canvas_webgl_minimal/Cargo.toml | 6 +- examples/canvas_webgl_minimal/src/lib.rs | 5 - renderer/src/gpu/renderer.rs | 4 + web_canvas/Cargo.toml | 50 ++++++ web_canvas/src/lib.rs | 196 +++++++++++++++++++++++ 9 files changed, 320 insertions(+), 49 deletions(-) create mode 100644 web_canvas/Cargo.toml create mode 100644 web_canvas/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 026cde47..2977f747 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,24 +202,6 @@ dependencies = [ "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "canvas-webgl-minimal" -version = "0.1.0" -dependencies = [ - "pathfinder_canvas 0.5.0", - "pathfinder_color 0.5.0", - "pathfinder_content 0.5.0", - "pathfinder_geometry 0.5.1", - "pathfinder_gl 0.5.0", - "pathfinder_gpu 0.5.0", - "pathfinder_renderer 0.5.0", - "pathfinder_resources 0.5.0", - "pathfinder_webgl 0.1.0", - "wasm-bindgen 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-test 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "web-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "canvas_glutin_minimal" version = "0.1.0" @@ -335,6 +317,24 @@ dependencies = [ "sdl2-sys 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "canvas_webgl_minimal" +version = "0.1.0" +dependencies = [ + "pathfinder_canvas 0.5.0", + "pathfinder_color 0.5.0", + "pathfinder_content 0.5.0", + "pathfinder_geometry 0.5.1", + "pathfinder_gl 0.5.0", + "pathfinder_gpu 0.5.0", + "pathfinder_renderer 0.5.0", + "pathfinder_resources 0.5.0", + "pathfinder_webgl 0.1.0", + "wasm-bindgen 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-test 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "web-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cbindgen" version = "0.13.2" @@ -671,6 +671,14 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "css-color-parser" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "data-url" version = "0.1.0" @@ -1299,6 +1307,11 @@ dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "lazy_static" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "lazy_static" version = "1.4.0" @@ -2015,6 +2028,25 @@ dependencies = [ "serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pathfinder_web_canvas" +version = "0.1.0" +dependencies = [ + "css-color-parser 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "pathfinder_canvas 0.5.0", + "pathfinder_color 0.5.0", + "pathfinder_content 0.5.0", + "pathfinder_geometry 0.5.1", + "pathfinder_gl 0.5.0", + "pathfinder_gpu 0.5.0", + "pathfinder_renderer 0.5.0", + "pathfinder_resources 0.5.0", + "pathfinder_webgl 0.1.0", + "wasm-bindgen 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-test 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "web-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pathfinder_webgl" version = "0.1.0" @@ -3216,6 +3248,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" "checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" "checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +"checksum css-color-parser 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ccb6ce7ef97e6dc6e575e51b596c9889a5cc88a307b5ef177d215c61fd7581d" "checksum data-url 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d33fe99ccedd6e84bc035f1931bb2e6be79739d6242bd895e7311c886c50dc9c" "checksum deflate 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e7e5d2a2273fed52a7f947ee55b092c4057025d7a3e04e5ecdbd25d6c3fb1bd7" "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" @@ -3283,6 +3316,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum khronos 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c0711aaa80e6ba6eb1fa8978f1f46bfcb38ceb2f3f33f3736efbff39dac89f50" "checksum khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" "checksum kurbo 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2520c9c9010461ec2b4573599bca458272319a314fd0b9476cacfcb9b6e5adc8" +"checksum lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "cf186d1a8aa5f5bee5fd662bc9c1b949e0259e1bcc379d1f006847b0080c7417" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" "checksum leak 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73" diff --git a/Cargo.toml b/Cargo.toml index 95c2cddc..9c675c57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ members = [ "utils/gamma-lut", "utils/svg-to-skia", "utils/convert", + "web_canvas", "webgl", ] diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index 3dc0a8bc..2cf4d091 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -89,6 +89,15 @@ impl Canvas { Canvas { scene } } + /// Returns the inner scene, replacing it with a blank scene. + #[inline] + pub fn take_scene(&mut self) -> Scene { + let view_box = self.scene.view_box(); + let mut new_scene = Scene::new(); + new_scene.set_view_box(view_box); + mem::replace(&mut self.scene, new_scene) + } + #[inline] pub fn into_scene(self) -> Scene { self.scene @@ -131,6 +140,11 @@ impl CanvasRenderingContext2D { &self.canvas } + #[inline] + pub fn canvas_mut(&mut self) -> &mut Canvas { + &mut self.canvas + } + #[inline] pub fn into_canvas(self) -> Canvas { self.canvas diff --git a/canvas/src/text.rs b/canvas/src/text.rs index 44af8a19..2192413e 100644 --- a/canvas/src/text.rs +++ b/canvas/src/text.rs @@ -446,29 +446,6 @@ impl IntoFontCollection for Vec { } } -/* -impl IntoFontCollection for Handle { - #[inline] - fn into_font_collection(self, context: &CanvasFontContext) -> Arc { - self.load().expect("Failed to load the font!").into_font_collection(context) - } -} - -impl<'a> IntoFontCollection for &'a [Handle] { - #[inline] - fn into_font_collection(self, context: &CanvasFontContext) -> Arc { - let mut font_collection = FontCollection::new(); - for handle in self { - let postscript_name = handle.postscript_name(); - - let font = handle.load().expect("Failed to load the font!"); - font_collection.add_family(FontFamily::new_from_font(font)); - } - Arc::new(font_collection) - } -} -*/ - impl IntoFontCollection for Font { #[inline] fn into_font_collection(self, context: &CanvasFontContext) -> Arc { diff --git a/examples/canvas_webgl_minimal/Cargo.toml b/examples/canvas_webgl_minimal/Cargo.toml index 501f7ef5..5aec1bad 100644 --- a/examples/canvas_webgl_minimal/Cargo.toml +++ b/examples/canvas_webgl_minimal/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "canvas-webgl-minimal" +name = "canvas_webgl_minimal" version = "0.1.0" authors = ["Patrick Walton "] edition = "2018" @@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"] default = [] [dependencies] -wasm-bindgen = "0.2.63" +wasm-bindgen = "0.2" [dependencies.pathfinder_canvas] path = "../../canvas" @@ -45,7 +45,7 @@ version = "0.3" features = ["Window"] [dev-dependencies] -wasm-bindgen-test = "0.3.13" +wasm-bindgen-test = "0.3" [profile.release] # Tell `rustc` to optimize for small code size. diff --git a/examples/canvas_webgl_minimal/src/lib.rs b/examples/canvas_webgl_minimal/src/lib.rs index 317e3356..bfa17b37 100644 --- a/examples/canvas_webgl_minimal/src/lib.rs +++ b/examples/canvas_webgl_minimal/src/lib.rs @@ -24,11 +24,6 @@ use web_sys::{self, HtmlCanvasElement, WebGl2RenderingContext}; mod utils; -#[wasm_bindgen] -extern { - fn alert(s: &str); -} - #[wasm_bindgen] pub fn rust_main() { utils::set_panic_hook(); diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 01a54912..91fe4edf 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -290,6 +290,10 @@ impl Renderer where D: Device { } } + pub fn destroy(self) -> D { + self.core.device + } + pub fn begin_scene(&mut self) { self.core.framebuffer_flags = FramebufferFlags::empty(); diff --git a/web_canvas/Cargo.toml b/web_canvas/Cargo.toml new file mode 100644 index 00000000..c5898ee9 --- /dev/null +++ b/web_canvas/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "pathfinder_web_canvas" +version = "0.1.0" +authors = ["Patrick Walton "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +css-color-parser = "0.1" +wasm-bindgen = "0.2" + +[dependencies.pathfinder_canvas] +path = "../canvas" + +[dependencies.pathfinder_color] +path = "../color" + +[dependencies.pathfinder_content] +path = "../content" + +[dependencies.pathfinder_geometry] +path = "../geometry" + +[dependencies.pathfinder_gl] +path = "../gl" + +[dependencies.pathfinder_gpu] +path = "../gpu" + +[dependencies.pathfinder_renderer] +path = "../renderer" + +[dependencies.pathfinder_resources] +path = "../resources" + +[dependencies.pathfinder_webgl] +path = "../webgl" + +[dependencies.web-sys] +version = "0.3" +features = ["Window", "console"] + +[dev-dependencies] +wasm-bindgen-test = "0.3" + +[profile.release] +# Tell `rustc` to optimize for small code size. +opt-level = "s" diff --git a/web_canvas/src/lib.rs b/web_canvas/src/lib.rs new file mode 100644 index 00000000..9fbe322d --- /dev/null +++ b/web_canvas/src/lib.rs @@ -0,0 +1,196 @@ +// pathfinder/web_canvas/src/lib.rs +// +// Copyright © 2020 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// 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_geometry::rect::RectF; +use pathfinder_geometry::transform2d::Transform2F; +use pathfinder_geometry::vector::{Vector2I, 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 wasm_bindgen::JsCast; +use wasm_bindgen::prelude::*; +use web_sys::{self, console, HtmlCanvasElement, WebGl2RenderingContext}; + +#[wasm_bindgen] +pub struct PFCanvasRenderingContext2D { + html_canvas: HtmlCanvasElement, + context: CanvasRenderingContext2D, + resource_loader: EmbeddedResourceLoader, + renderer: Option>, + default_path: Path2D, + // FIXME(pcwalton): Remove this once renderers are resizable. + prev_framebuffer_size: Vector2I, +} + +#[wasm_bindgen(js_name = "createContext")] +pub fn create_context(html_canvas: HtmlCanvasElement) -> PFCanvasRenderingContext2D { + let context = html_canvas.get_context("webgl2") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap(); + + // Get the real size of the window, taking HiDPI into account. + let framebuffer_size = vec2i(html_canvas.width() as i32, html_canvas.height() as i32); + + // Create a Pathfinder GL device. + let pathfinder_device = WebGlDevice::new(context); + + // Create a Pathfinder renderer. + let mode = RendererMode::default_for_device(&pathfinder_device); + let options = RendererOptions { + dest: DestFramebuffer::full_window(framebuffer_size), + background_color: Some(ColorF::white()), + ..RendererOptions::default() + }; + let resource_loader = EmbeddedResourceLoader::new(); + let renderer = Renderer::new(pathfinder_device, &resource_loader, mode, options); + + // Make a canvas. + let font_context = CanvasFontContext::from_system_source(); + let context = Canvas::new(framebuffer_size.to_f32()).get_context_2d(font_context); + + PFCanvasRenderingContext2D { + html_canvas, + context, + resource_loader, + renderer: Some(renderer), + default_path: Path2D::new(), + prev_framebuffer_size: framebuffer_size, + } +} + +#[wasm_bindgen] +impl PFCanvasRenderingContext2D { + pub fn 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; + } + + let mut scene = self.context.canvas_mut().take_scene(); + scene.build_and_render(self.renderer.as_mut().unwrap(), + BuildOptions::default(), + SequentialExecutor); + } + + #[wasm_bindgen(getter)] + pub fn canvas(&self) -> HtmlCanvasElement { + 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); + } + + pub fn save(&mut self) { + self.context.save(); + } + + pub fn restore(&mut self) { + self.context.restore(); + } + + #[wasm_bindgen(js_name = "fillRect")] + pub fn fill_rect(&mut self, x: f32, y: f32, width: f32, height: f32) { + self.context.fill_rect(RectF::new(vec2f(x, y), vec2f(width, height))); + } + + 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) + } + + pub fn translate(&mut self, x: f32, y: f32) { + self.context.translate(vec2f(x, y)) + } + + pub fn scale(&mut self, x: f32, y: f32) { + self.context.scale(vec2f(x, y)) + } + + pub fn rotate(&mut self, angle: f32) { + self.context.rotate(angle) + } + + #[wasm_bindgen(js_name = "beginPath")] + pub fn begin_path(&mut self) { + self.default_path = Path2D::new(); + } + + #[wasm_bindgen(js_name = "moveTo")] + pub fn move_to(&mut self, x: f32, y: f32) { + self.default_path.move_to(vec2f(x, y)) + } + + #[wasm_bindgen(js_name = "lineTo")] + pub fn line_to(&mut self, x: f32, y: f32) { + self.default_path.line_to(vec2f(x, y)) + } + + #[wasm_bindgen(js_name = "bezierCurveTo")] + pub fn bezier_curve_to(&mut self, cp1x: f32, cp1y: f32, cp2x: f32, cp2y: f32, x: f32, y: f32) { + self.default_path.bezier_curve_to(vec2f(cp1x, cp1y), vec2f(cp2x, cp2y), vec2f(x, y)) + } + + #[wasm_bindgen(js_name = "quadraticCurveTo")] + pub fn quadratic_curve_to(&mut self, cpx: f32, cpy: f32, x: f32, y: f32) { + self.default_path.quadratic_curve_to(vec2f(cpx, cpy), vec2f(x, y)) + } + + #[wasm_bindgen(js_name = "closePath")] + pub fn close_path(&mut self) { + self.default_path.close_path(); + } + + pub fn fill(&mut self) { + let path = self.default_path.clone(); + self.context.fill_path(path, FillRule::Winding); + } + + pub fn stroke(&mut self) { + let path = self.default_path.clone(); + self.context.stroke_path(path); + } +} From a49714e0ba3b1f0742b4a491399f85ee8713411a Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 6 Jul 2020 11:01:23 -0700 Subject: [PATCH 2/2] 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)) }