From e54e0e4760d4c64e2b55421999587262eedf8f3c Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sat, 4 Jul 2020 09:33:22 -0700 Subject: [PATCH] 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); + } +}