Auto merge of #387 - pcwalton:web-canvas, r=pcwalton

Flesh out the `web_canvas` crate a bit more
This commit is contained in:
bors-servo 2020-07-06 14:13:04 -04:00 committed by GitHub
commit b97c92ca8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 192 additions and 58 deletions

View File

@ -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<f32>) {
// 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);
}

View File

@ -292,7 +292,9 @@ impl<W> DemoApp<W> 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) => {

View File

@ -489,7 +489,8 @@ impl<D> Renderer<D> 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);
}

View File

@ -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

View File

@ -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<WebGlDevice>>,
renderer: Renderer<WebGlDevice>,
default_path: Path2D,
// FIXME(pcwalton): Remove this once renderers are resizable.
prev_framebuffer_size: Vector2I,
current_state: WebCanvasState,
saved_states: Vec<WebCanvasState>,
}
#[derive(Clone)]
struct WebCanvasState {
fill_style_string: Arc<String>,
stroke_style_string: Arc<String>,
}
#[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<FillStyle> {
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))
}