2020-07-04 12:33:22 -04:00
|
|
|
// pathfinder/web_canvas/src/lib.rs
|
|
|
|
//
|
|
|
|
// Copyright © 2020 The Pathfinder Project Developers.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
|
|
|
use css_color_parser::Color;
|
2020-07-06 14:01:23 -04:00
|
|
|
use pathfinder_canvas::{Canvas, CanvasFontContext, CanvasRenderingContext2D, FillRule, FillStyle};
|
|
|
|
use pathfinder_canvas::{LineCap, Path2D};
|
|
|
|
use pathfinder_color::ColorU;
|
2020-07-04 12:33:22 -04:00
|
|
|
use pathfinder_geometry::rect::RectF;
|
|
|
|
use pathfinder_geometry::transform2d::Transform2F;
|
2020-07-06 14:01:23 -04:00
|
|
|
use pathfinder_geometry::vector::{vec2f, vec2i};
|
2020-07-04 12:33:22 -04:00
|
|
|
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::str::FromStr;
|
2020-07-06 14:01:23 -04:00
|
|
|
use std::sync::Arc;
|
2020-07-04 12:33:22 -04:00
|
|
|
use wasm_bindgen::JsCast;
|
|
|
|
use wasm_bindgen::prelude::*;
|
2020-07-06 14:01:23 -04:00
|
|
|
use web_sys::{self, HtmlCanvasElement, WebGl2RenderingContext};
|
2020-07-04 12:33:22 -04:00
|
|
|
|
|
|
|
#[wasm_bindgen]
|
|
|
|
pub struct PFCanvasRenderingContext2D {
|
|
|
|
html_canvas: HtmlCanvasElement,
|
|
|
|
context: CanvasRenderingContext2D,
|
2020-07-06 14:01:23 -04:00
|
|
|
renderer: Renderer<WebGlDevice>,
|
2020-07-04 12:33:22 -04:00
|
|
|
default_path: Path2D,
|
2020-07-06 14:01:23 -04:00
|
|
|
current_state: WebCanvasState,
|
|
|
|
saved_states: Vec<WebCanvasState>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct WebCanvasState {
|
|
|
|
fill_style_string: Arc<String>,
|
|
|
|
stroke_style_string: Arc<String>,
|
2020-07-04 12:33:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[wasm_bindgen(js_name = "createContext")]
|
|
|
|
pub fn create_context(html_canvas: HtmlCanvasElement) -> PFCanvasRenderingContext2D {
|
|
|
|
let context = html_canvas.get_context("webgl2")
|
|
|
|
.unwrap()
|
|
|
|
.unwrap()
|
|
|
|
.dyn_into::<WebGl2RenderingContext>()
|
|
|
|
.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),
|
2020-07-06 14:01:23 -04:00
|
|
|
background_color: None,
|
2020-07-04 12:33:22 -04:00
|
|
|
..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,
|
2020-07-06 14:01:23 -04:00
|
|
|
renderer,
|
2020-07-04 12:33:22 -04:00
|
|
|
default_path: Path2D::new(),
|
2020-07-06 14:01:23 -04:00
|
|
|
current_state: WebCanvasState {
|
|
|
|
fill_style_string: Arc::new("black".to_owned()),
|
|
|
|
stroke_style_string: Arc::new("black".to_owned()),
|
|
|
|
},
|
|
|
|
saved_states: vec![],
|
2020-07-04 12:33:22 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[wasm_bindgen]
|
|
|
|
impl PFCanvasRenderingContext2D {
|
2020-07-06 14:01:23 -04:00
|
|
|
#[wasm_bindgen(js_name = "pfFlush")]
|
|
|
|
pub fn pf_flush(&mut self) {
|
2020-07-04 12:33:22 -04:00
|
|
|
// Update framebuffer size.
|
|
|
|
let framebuffer_size = vec2i(self.html_canvas.width() as i32,
|
|
|
|
self.html_canvas.height() as i32);
|
2020-07-06 14:01:23 -04:00
|
|
|
self.renderer.options_mut().dest = DestFramebuffer::full_window(framebuffer_size);
|
|
|
|
self.renderer.options_mut().background_color = None;
|
|
|
|
self.renderer.dest_framebuffer_size_changed();
|
|
|
|
|
|
|
|
// TODO(pcwalton): This is inefficient!
|
|
|
|
let mut scene = (*self.context.canvas_mut().scene()).clone();
|
|
|
|
scene.build_and_render(&mut self.renderer, BuildOptions::default(), SequentialExecutor);
|
2020-07-04 12:33:22 -04:00
|
|
|
|
2020-07-06 14:01:23 -04:00
|
|
|
self.context.canvas_mut().set_size(framebuffer_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[wasm_bindgen(js_name = "pfClear")]
|
|
|
|
pub fn pf_clear(&mut self) {
|
|
|
|
self.context.clear();
|
2020-07-04 12:33:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[wasm_bindgen(getter)]
|
|
|
|
pub fn canvas(&self) -> HtmlCanvasElement {
|
|
|
|
self.html_canvas.clone()
|
|
|
|
}
|
|
|
|
|
2020-07-06 14:01:23 -04:00
|
|
|
// Drawing rectangles
|
|
|
|
|
|
|
|
#[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")]
|
|
|
|
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)));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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")]
|
2020-07-04 12:33:22 -04:00
|
|
|
#[wasm_bindgen(setter)]
|
2020-07-06 14:01:23 -04:00
|
|
|
pub fn set_line_width(&mut self, new_line_width: f32) {
|
|
|
|
self.context.set_line_width(new_line_width);
|
2020-07-04 12:33:22 -04:00
|
|
|
}
|
|
|
|
|
2020-07-06 14:01:23 -04:00
|
|
|
#[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(),
|
|
|
|
}
|
2020-07-04 12:33:22 -04:00
|
|
|
}
|
|
|
|
|
2020-07-06 14:01:23 -04:00
|
|
|
#[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)
|
|
|
|
}
|
2020-07-04 12:33:22 -04:00
|
|
|
}
|
|
|
|
|
2020-07-06 14:01:23 -04:00
|
|
|
#[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());
|
|
|
|
}
|
2020-07-04 12:33:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2020-07-06 14:01:23 -04:00
|
|
|
|
|
|
|
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))
|
2020-07-04 12:33:22 -04:00
|
|
|
}
|