diff options
Diffstat (limited to 'components/script/canvas_state.rs')
-rw-r--r-- | components/script/canvas_state.rs | 1791 |
1 files changed, 1791 insertions, 0 deletions
diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs new file mode 100644 index 00000000000..0a6fba842e4 --- /dev/null +++ b/components/script/canvas_state.rs @@ -0,0 +1,1791 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasDirection; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasFillRule; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasImageSource; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasTextAlign; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasTextBaseline; +use crate::dom::bindings::codegen::Bindings::ImageDataBinding::ImageDataMethods; +use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern; +use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::canvasgradient::{CanvasGradient, CanvasGradientStyle, ToFillOrStrokeStyle}; +use crate::dom::canvaspattern::CanvasPattern; +use crate::dom::dommatrix::DOMMatrix; +use crate::dom::element::cors_setting_for_element; +use crate::dom::element::Element; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlcanvaselement::{CanvasContext, HTMLCanvasElement}; +use crate::dom::imagedata::ImageData; +use crate::dom::node::{window_from_node, Node, NodeDamage}; +use crate::dom::offscreencanvas::{OffscreenCanvas, OffscreenCanvasContext}; +use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; +use crate::dom::textmetrics::TextMetrics; +use crate::unpremultiplytable::UNPREMULTIPLY_TABLE; +use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg, Direction, TextAlign, TextBaseline}; +use canvas_traits::canvas::{CompositionOrBlending, FillOrStrokeStyle, FillRule}; +use canvas_traits::canvas::{LineCapStyle, LineJoinStyle, LinearGradientStyle}; +use canvas_traits::canvas::{RadialGradientStyle, RepetitionStyle}; +use cssparser::Color as CSSColor; +use cssparser::{Parser, ParserInput, RGBA}; +use euclid::{ + default::{Point2D, Rect, Size2D, Transform2D}, + vec2, +}; +use ipc_channel::ipc::{self, IpcSender}; +use net_traits::image_cache::{ImageCache, ImageResponse}; +use net_traits::request::CorsSettings; +use pixels::PixelFormat; +use profile_traits::ipc as profiled_ipc; +use script_traits::ScriptMsg; +use serde_bytes::ByteBuf; +use servo_url::{ImmutableOrigin, ServoUrl}; +use std::cell::Cell; +use std::fmt; +use std::str::FromStr; +use std::sync::Arc; +use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps; +use style::properties::style_structs::Font; +use style::values::computed::font::FontStyle; +use style_traits::values::ToCss; + +#[unrooted_must_root_lint::must_root] +#[derive(Clone, JSTraceable, MallocSizeOf)] +#[allow(dead_code)] +pub(crate) enum CanvasFillOrStrokeStyle { + Color(RGBA), + Gradient(Dom<CanvasGradient>), + Pattern(Dom<CanvasPattern>), +} + +impl CanvasFillOrStrokeStyle { + fn to_fill_or_stroke_style(&self) -> FillOrStrokeStyle { + match self { + CanvasFillOrStrokeStyle::Color(rgba) => FillOrStrokeStyle::Color(*rgba), + CanvasFillOrStrokeStyle::Gradient(gradient) => gradient.to_fill_or_stroke_style(), + CanvasFillOrStrokeStyle::Pattern(pattern) => pattern.to_fill_or_stroke_style(), + } + } +} + +#[unrooted_must_root_lint::must_root] +#[derive(Clone, JSTraceable, MallocSizeOf)] +pub(crate) struct CanvasContextState { + global_alpha: f64, + global_composition: CompositionOrBlending, + image_smoothing_enabled: bool, + fill_style: CanvasFillOrStrokeStyle, + stroke_style: CanvasFillOrStrokeStyle, + line_width: f64, + line_cap: LineCapStyle, + line_join: LineJoinStyle, + miter_limit: f64, + transform: Transform2D<f32>, + shadow_offset_x: f64, + shadow_offset_y: f64, + shadow_blur: f64, + shadow_color: RGBA, + font_style: Option<Font>, + text_align: TextAlign, + text_baseline: TextBaseline, + direction: Direction, +} + +impl CanvasContextState { + const DEFAULT_FONT_STYLE: &'static str = "10px sans-serif"; + + pub(crate) fn new() -> CanvasContextState { + let black = RGBA::new(0, 0, 0, 255); + CanvasContextState { + global_alpha: 1.0, + global_composition: CompositionOrBlending::default(), + image_smoothing_enabled: true, + fill_style: CanvasFillOrStrokeStyle::Color(black), + stroke_style: CanvasFillOrStrokeStyle::Color(black), + line_width: 1.0, + line_cap: LineCapStyle::Butt, + line_join: LineJoinStyle::Miter, + miter_limit: 10.0, + transform: Transform2D::identity(), + shadow_offset_x: 0.0, + shadow_offset_y: 0.0, + shadow_blur: 0.0, + shadow_color: RGBA::transparent(), + font_style: None, + text_align: Default::default(), + text_baseline: Default::default(), + direction: Default::default(), + } + } +} + +#[unrooted_must_root_lint::must_root] +#[derive(JSTraceable, MallocSizeOf)] +pub(crate) struct CanvasState { + #[ignore_malloc_size_of = "Defined in ipc-channel"] + ipc_renderer: IpcSender<CanvasMsg>, + canvas_id: CanvasId, + state: DomRefCell<CanvasContextState>, + origin_clean: Cell<bool>, + #[ignore_malloc_size_of = "Arc"] + image_cache: Arc<dyn ImageCache>, + /// The base URL for resolving CSS image URL values. + /// Needed because of https://github.com/servo/servo/issues/17625 + base_url: ServoUrl, + origin: ImmutableOrigin, + /// Any missing image URLs. + missing_image_urls: DomRefCell<Vec<ServoUrl>>, + saved_states: DomRefCell<Vec<CanvasContextState>>, +} + +impl CanvasState { + pub(crate) fn new(global: &GlobalScope, size: Size2D<u64>) -> CanvasState { + debug!("Creating new canvas rendering context."); + let (sender, receiver) = + profiled_ipc::channel(global.time_profiler_chan().clone()).unwrap(); + let script_to_constellation_chan = global.script_to_constellation_chan(); + debug!("Asking constellation to create new canvas thread."); + script_to_constellation_chan + .send(ScriptMsg::CreateCanvasPaintThread(size, sender)) + .unwrap(); + let (ipc_renderer, canvas_id) = receiver.recv().unwrap(); + debug!("Done."); + // Worklets always receive a unique origin. This messes with fetching + // cached images in the case of paint worklets, since the image cache + // is keyed on the origin requesting the image data. + let origin = if global.is::<PaintWorkletGlobalScope>() { + global.api_base_url().origin() + } else { + global.origin().immutable().clone() + }; + CanvasState { + ipc_renderer: ipc_renderer, + canvas_id: canvas_id, + state: DomRefCell::new(CanvasContextState::new()), + origin_clean: Cell::new(true), + image_cache: global.image_cache(), + base_url: global.api_base_url(), + missing_image_urls: DomRefCell::new(Vec::new()), + saved_states: DomRefCell::new(Vec::new()), + origin, + } + } + + pub fn get_ipc_renderer(&self) -> &IpcSender<CanvasMsg> { + &self.ipc_renderer + } + + pub fn get_missing_image_urls(&self) -> &DomRefCell<Vec<ServoUrl>> { + &self.missing_image_urls + } + + pub fn get_canvas_id(&self) -> CanvasId { + self.canvas_id.clone() + } + + pub fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) { + self.ipc_renderer + .send(CanvasMsg::Canvas2d(msg, self.get_canvas_id())) + .unwrap() + } + + // https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions + pub fn set_bitmap_dimensions(&self, size: Size2D<u64>) { + self.reset_to_initial_state(); + self.ipc_renderer + .send(CanvasMsg::Recreate(size, self.get_canvas_id())) + .unwrap(); + } + + pub fn reset_to_initial_state(&self) { + self.saved_states.borrow_mut().clear(); + *self.state.borrow_mut() = CanvasContextState::new(); + } + + fn create_drawable_rect(&self, x: f64, y: f64, w: f64, h: f64) -> Option<Rect<f32>> { + if !([x, y, w, h].iter().all(|val| val.is_finite())) { + return None; + } + + if w == 0.0 && h == 0.0 { + return None; + } + + Some(Rect::new( + Point2D::new(x as f32, y as f32), + Size2D::new(w as f32, h as f32), + )) + } + + pub fn origin_is_clean(&self) -> bool { + self.origin_clean.get() + } + + fn set_origin_unclean(&self) { + self.origin_clean.set(false) + } + + // https://html.spec.whatwg.org/multipage/#the-image-argument-is-not-origin-clean + fn is_origin_clean(&self, image: CanvasImageSource) -> bool { + match image { + CanvasImageSource::HTMLCanvasElement(canvas) => canvas.origin_is_clean(), + CanvasImageSource::OffscreenCanvas(canvas) => canvas.origin_is_clean(), + CanvasImageSource::HTMLImageElement(image) => { + image.same_origin(GlobalScope::entry().origin()) + }, + CanvasImageSource::CSSStyleValue(_) => true, + } + } + + fn fetch_image_data( + &self, + url: ServoUrl, + cors_setting: Option<CorsSettings>, + ) -> Option<(Vec<u8>, Size2D<u32>)> { + let img = match self.request_image_from_cache(url, cors_setting) { + ImageResponse::Loaded(img, _) => img, + ImageResponse::PlaceholderLoaded(_, _) | + ImageResponse::None | + ImageResponse::MetadataLoaded(_) => { + return None; + }, + }; + + let image_size = Size2D::new(img.width, img.height); + let image_data = match img.format { + PixelFormat::BGRA8 => img.bytes.to_vec(), + pixel_format => unimplemented!("unsupported pixel format ({:?})", pixel_format), + }; + + Some((image_data, image_size)) + } + + fn request_image_from_cache( + &self, + url: ServoUrl, + cors_setting: Option<CorsSettings>, + ) -> ImageResponse { + match self + .image_cache + .get_image(url.clone(), self.origin.clone(), cors_setting) + { + Some(image) => ImageResponse::Loaded(image, url), + None => { + // Rather annoyingly, we get the same response back from + // A load which really failed and from a load which hasn't started yet. + self.missing_image_urls.borrow_mut().push(url); + ImageResponse::None + }, + } + } + + fn parse_color(&self, canvas: Option<&HTMLCanvasElement>, string: &str) -> Result<RGBA, ()> { + let mut input = ParserInput::new(string); + let mut parser = Parser::new(&mut input); + let color = CSSColor::parse(&mut parser); + if parser.is_exhausted() { + match color { + Ok(CSSColor::RGBA(rgba)) => Ok(rgba), + Ok(CSSColor::CurrentColor) => { + // TODO: https://github.com/whatwg/html/issues/1099 + // Reconsider how to calculate currentColor in a display:none canvas + + // TODO: will need to check that the context bitmap mode is fixed + // once we implement CanvasProxy + let canvas = match canvas { + // https://drafts.css-houdini.org/css-paint-api/#2d-rendering-context + // Whenever "currentColor" is used as a color in the PaintRenderingContext2D API, + // it is treated as opaque black. + None => return Ok(RGBA::new(0, 0, 0, 255)), + Some(ref canvas) => &**canvas, + }; + + let canvas_element = canvas.upcast::<Element>(); + + match canvas_element.style() { + Some(ref s) if canvas_element.has_css_layout_box() => { + Ok(s.get_inherited_text().color) + }, + _ => Ok(RGBA::new(0, 0, 0, 255)), + } + }, + _ => Err(()), + } + } else { + Err(()) + } + } + + pub fn get_rect(&self, canvas_size: Size2D<u64>, rect: Rect<u64>) -> Vec<u8> { + assert!(self.origin_is_clean()); + + assert!(Rect::from_size(canvas_size).contains_rect(&rect)); + + let (sender, receiver) = ipc::bytes_channel().unwrap(); + self.send_canvas_2d_msg(Canvas2dMsg::GetImageData(rect, canvas_size, sender)); + let mut pixels = receiver.recv().unwrap().to_vec(); + + for chunk in pixels.chunks_mut(4) { + let b = chunk[0]; + chunk[0] = UNPREMULTIPLY_TABLE[256 * (chunk[3] as usize) + chunk[2] as usize]; + chunk[1] = UNPREMULTIPLY_TABLE[256 * (chunk[3] as usize) + chunk[1] as usize]; + chunk[2] = UNPREMULTIPLY_TABLE[256 * (chunk[3] as usize) + b as usize]; + } + + pixels + } + + // + // drawImage coordinates explained + // + // Source Image Destination Canvas + // +-------------+ +-------------+ + // | | | | + // |(sx,sy) | |(dx,dy) | + // | +----+ | | +----+ | + // | | | | | | | | + // | | |sh |---->| | |dh | + // | | | | | | | | + // | +----+ | | +----+ | + // | sw | | dw | + // | | | | + // +-------------+ +-------------+ + // + // + // The rectangle (sx, sy, sw, sh) from the source image + // is copied on the rectangle (dx, dy, dh, dw) of the destination canvas + // + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage + fn draw_image_internal( + &self, + htmlcanvas: Option<&HTMLCanvasElement>, + image: CanvasImageSource, + sx: f64, + sy: f64, + sw: Option<f64>, + sh: Option<f64>, + dx: f64, + dy: f64, + dw: Option<f64>, + dh: Option<f64>, + ) -> ErrorResult { + let result = match image { + CanvasImageSource::HTMLCanvasElement(ref canvas) => { + // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument + if !canvas.is_valid() { + return Err(Error::InvalidState); + } + + self.draw_html_canvas_element(&canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh) + }, + CanvasImageSource::OffscreenCanvas(ref canvas) => { + // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument + if !canvas.is_valid() { + return Err(Error::InvalidState); + } + + self.draw_offscreen_canvas(&canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh) + }, + CanvasImageSource::HTMLImageElement(ref image) => { + // https://html.spec.whatwg.org/multipage/#drawing-images + // 2. Let usability be the result of checking the usability of image. + // 3. If usability is bad, then return (without drawing anything). + if !image.is_usable()? { + return Ok(()); + } + + // TODO(pylbrecht): is it possible for image.get_url() to return None after the usability check? + // https://html.spec.whatwg.org/multipage/#img-error + // If the image argument is an HTMLImageElement object that is in the broken state, + // then throw an InvalidStateError exception + let url = image.get_url().ok_or(Error::InvalidState)?; + let cors_setting = cors_setting_for_element(image.upcast()); + self.fetch_and_draw_image_data( + htmlcanvas, + url, + cors_setting, + sx, + sy, + sw, + sh, + dx, + dy, + dw, + dh, + ) + }, + CanvasImageSource::CSSStyleValue(ref value) => { + let url = value + .get_url(self.base_url.clone()) + .ok_or(Error::InvalidState)?; + self.fetch_and_draw_image_data( + htmlcanvas, url, None, sx, sy, sw, sh, dx, dy, dw, dh, + ) + }, + }; + + if result.is_ok() && !self.is_origin_clean(image) { + self.set_origin_unclean() + } + result + } + + fn draw_offscreen_canvas( + &self, + canvas: &OffscreenCanvas, + htmlcanvas: Option<&HTMLCanvasElement>, + sx: f64, + sy: f64, + sw: Option<f64>, + sh: Option<f64>, + dx: f64, + dy: f64, + dw: Option<f64>, + dh: Option<f64>, + ) -> ErrorResult { + let canvas_size = canvas.get_size(); + let dw = dw.unwrap_or(canvas_size.width as f64); + let dh = dh.unwrap_or(canvas_size.height as f64); + let sw = sw.unwrap_or(canvas_size.width as f64); + let sh = sh.unwrap_or(canvas_size.height as f64); + + let image_size = Size2D::new(canvas_size.width as f64, canvas_size.height as f64); + // 2. Establish the source and destination rectangles + let (source_rect, dest_rect) = + self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh); + + if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) { + return Ok(()); + } + + let smoothing_enabled = self.state.borrow().image_smoothing_enabled; + + if let Some(context) = canvas.context() { + match *context { + OffscreenCanvasContext::OffscreenContext2d(ref context) => { + context.send_canvas_2d_msg(Canvas2dMsg::DrawImageInOther( + self.get_canvas_id(), + image_size, + dest_rect, + source_rect, + smoothing_enabled, + )); + }, + } + } else { + self.send_canvas_2d_msg(Canvas2dMsg::DrawImage( + None, + image_size, + dest_rect, + source_rect, + smoothing_enabled, + )); + } + + self.mark_as_dirty(htmlcanvas); + Ok(()) + } + + fn draw_html_canvas_element( + &self, + canvas: &HTMLCanvasElement, // source canvas + htmlcanvas: Option<&HTMLCanvasElement>, // destination canvas + sx: f64, + sy: f64, + sw: Option<f64>, + sh: Option<f64>, + dx: f64, + dy: f64, + dw: Option<f64>, + dh: Option<f64>, + ) -> ErrorResult { + // 1. Check the usability of the image argument + if !canvas.is_valid() { + return Err(Error::InvalidState); + } + + let canvas_size = canvas.get_size(); + let dw = dw.unwrap_or(canvas_size.width as f64); + let dh = dh.unwrap_or(canvas_size.height as f64); + let sw = sw.unwrap_or(canvas_size.width as f64); + let sh = sh.unwrap_or(canvas_size.height as f64); + + let image_size = Size2D::new(canvas_size.width as f64, canvas_size.height as f64); + // 2. Establish the source and destination rectangles + let (source_rect, dest_rect) = + self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh); + + if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) { + return Ok(()); + } + + let smoothing_enabled = self.state.borrow().image_smoothing_enabled; + + if let Some(context) = canvas.context() { + match *context { + CanvasContext::Context2d(ref context) => { + context.send_canvas_2d_msg(Canvas2dMsg::DrawImageInOther( + self.get_canvas_id(), + image_size, + dest_rect, + source_rect, + smoothing_enabled, + )); + }, + _ => return Err(Error::InvalidState), + } + } else { + self.send_canvas_2d_msg(Canvas2dMsg::DrawImage( + None, + image_size, + dest_rect, + source_rect, + smoothing_enabled, + )); + } + + self.mark_as_dirty(htmlcanvas); + Ok(()) + } + + fn fetch_and_draw_image_data( + &self, + canvas: Option<&HTMLCanvasElement>, + url: ServoUrl, + cors_setting: Option<CorsSettings>, + sx: f64, + sy: f64, + sw: Option<f64>, + sh: Option<f64>, + dx: f64, + dy: f64, + dw: Option<f64>, + dh: Option<f64>, + ) -> ErrorResult { + debug!("Fetching image {}.", url); + let (mut image_data, image_size) = self + .fetch_image_data(url, cors_setting) + .ok_or(Error::InvalidState)?; + pixels::rgba8_premultiply_inplace(&mut image_data); + let image_size = image_size.to_f64(); + + let dw = dw.unwrap_or(image_size.width); + let dh = dh.unwrap_or(image_size.height); + let sw = sw.unwrap_or(image_size.width); + let sh = sh.unwrap_or(image_size.height); + + // Establish the source and destination rectangles + let (source_rect, dest_rect) = + self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh); + + if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) { + return Ok(()); + } + + let smoothing_enabled = self.state.borrow().image_smoothing_enabled; + self.send_canvas_2d_msg(Canvas2dMsg::DrawImage( + Some(ByteBuf::from(image_data)), + image_size, + dest_rect, + source_rect, + smoothing_enabled, + )); + self.mark_as_dirty(canvas); + Ok(()) + } + + pub fn mark_as_dirty(&self, canvas: Option<&HTMLCanvasElement>) { + if let Some(ref canvas) = canvas { + canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + } + } + + // It is used by DrawImage to calculate the size of the source and destination rectangles based + // on the drawImage call arguments + // source rectangle = area of the original image to be copied + // destination rectangle = area of the destination canvas where the source image is going to be drawn + fn adjust_source_dest_rects( + &self, + image_size: Size2D<f64>, + sx: f64, + sy: f64, + sw: f64, + sh: f64, + dx: f64, + dy: f64, + dw: f64, + dh: f64, + ) -> (Rect<f64>, Rect<f64>) { + let image_rect = Rect::new( + Point2D::new(0f64, 0f64), + Size2D::new(image_size.width as f64, image_size.height as f64), + ); + + // The source rectangle is the rectangle whose corners are the four points (sx, sy), + // (sx+sw, sy), (sx+sw, sy+sh), (sx, sy+sh). + let source_rect = Rect::new( + Point2D::new(sx.min(sx + sw), sy.min(sy + sh)), + Size2D::new(sw.abs(), sh.abs()), + ); + + // When the source rectangle is outside the source image, + // the source rectangle must be clipped to the source image + let source_rect_clipped = source_rect + .intersection(&image_rect) + .unwrap_or(Rect::zero()); + + // Width and height ratios between the non clipped and clipped source rectangles + let width_ratio: f64 = source_rect_clipped.size.width / source_rect.size.width; + let height_ratio: f64 = source_rect_clipped.size.height / source_rect.size.height; + + // When the source rectangle is outside the source image, + // the destination rectangle must be clipped in the same proportion. + let dest_rect_width_scaled: f64 = dw * width_ratio; + let dest_rect_height_scaled: f64 = dh * height_ratio; + + // The destination rectangle is the rectangle whose corners are the four points (dx, dy), + // (dx+dw, dy), (dx+dw, dy+dh), (dx, dy+dh). + let dest_rect = Rect::new( + Point2D::new( + dx.min(dx + dest_rect_width_scaled), + dy.min(dy + dest_rect_height_scaled), + ), + Size2D::new(dest_rect_width_scaled.abs(), dest_rect_height_scaled.abs()), + ); + + let source_rect = Rect::new( + Point2D::new(source_rect_clipped.origin.x, source_rect_clipped.origin.y), + Size2D::new( + source_rect_clipped.size.width, + source_rect_clipped.size.height, + ), + ); + + (source_rect, dest_rect) + } + + fn update_transform(&self) { + self.send_canvas_2d_msg(Canvas2dMsg::SetTransform(self.state.borrow().transform)) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-fillrect + pub fn fill_rect(&self, x: f64, y: f64, width: f64, height: f64) { + if let Some(rect) = self.create_drawable_rect(x, y, width, height) { + let style = self.state.borrow().fill_style.to_fill_or_stroke_style(); + self.send_canvas_2d_msg(Canvas2dMsg::FillRect(rect, style)); + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clearrect + pub fn clear_rect(&self, x: f64, y: f64, width: f64, height: f64) { + if let Some(rect) = self.create_drawable_rect(x, y, width, height) { + self.send_canvas_2d_msg(Canvas2dMsg::ClearRect(rect)); + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokerect + pub fn stroke_rect(&self, x: f64, y: f64, width: f64, height: f64) { + if let Some(rect) = self.create_drawable_rect(x, y, width, height) { + let style = self.state.borrow().stroke_style.to_fill_or_stroke_style(); + self.send_canvas_2d_msg(Canvas2dMsg::StrokeRect(rect, style)); + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx + pub fn shadow_offset_x(&self) -> f64 { + self.state.borrow().shadow_offset_x + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx + pub fn set_shadow_offset_x(&self, value: f64) { + if !value.is_finite() || value == self.state.borrow().shadow_offset_x { + return; + } + self.state.borrow_mut().shadow_offset_x = value; + self.send_canvas_2d_msg(Canvas2dMsg::SetShadowOffsetX(value)) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety + pub fn shadow_offset_y(&self) -> f64 { + self.state.borrow().shadow_offset_y + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety + pub fn set_shadow_offset_y(&self, value: f64) { + if !value.is_finite() || value == self.state.borrow().shadow_offset_y { + return; + } + self.state.borrow_mut().shadow_offset_y = value; + self.send_canvas_2d_msg(Canvas2dMsg::SetShadowOffsetY(value)) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur + pub fn shadow_blur(&self) -> f64 { + self.state.borrow().shadow_blur + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur + pub fn set_shadow_blur(&self, value: f64) { + if !value.is_finite() || value < 0f64 || value == self.state.borrow().shadow_blur { + return; + } + self.state.borrow_mut().shadow_blur = value; + self.send_canvas_2d_msg(Canvas2dMsg::SetShadowBlur(value)) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor + pub fn shadow_color(&self) -> DOMString { + let mut result = String::new(); + serialize(&self.state.borrow().shadow_color, &mut result).unwrap(); + DOMString::from(result) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor + pub fn set_shadow_color(&self, value: DOMString) { + if let Ok(color) = parse_color(&value) { + self.state.borrow_mut().shadow_color = color; + self.send_canvas_2d_msg(Canvas2dMsg::SetShadowColor(color)) + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle + pub fn stroke_style(&self) -> StringOrCanvasGradientOrCanvasPattern { + match self.state.borrow().stroke_style { + CanvasFillOrStrokeStyle::Color(ref rgba) => { + let mut result = String::new(); + serialize(rgba, &mut result).unwrap(); + StringOrCanvasGradientOrCanvasPattern::String(DOMString::from(result)) + }, + CanvasFillOrStrokeStyle::Gradient(ref gradient) => { + StringOrCanvasGradientOrCanvasPattern::CanvasGradient(DomRoot::from_ref(&*gradient)) + }, + CanvasFillOrStrokeStyle::Pattern(ref pattern) => { + StringOrCanvasGradientOrCanvasPattern::CanvasPattern(DomRoot::from_ref(&*pattern)) + }, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle + pub fn set_stroke_style( + &self, + canvas: Option<&HTMLCanvasElement>, + value: StringOrCanvasGradientOrCanvasPattern, + ) { + match value { + StringOrCanvasGradientOrCanvasPattern::String(string) => { + if let Ok(rgba) = self.parse_color(canvas, &string) { + self.state.borrow_mut().stroke_style = CanvasFillOrStrokeStyle::Color(rgba); + } + }, + StringOrCanvasGradientOrCanvasPattern::CanvasGradient(gradient) => { + self.state.borrow_mut().stroke_style = + CanvasFillOrStrokeStyle::Gradient(Dom::from_ref(&*gradient)); + }, + StringOrCanvasGradientOrCanvasPattern::CanvasPattern(pattern) => { + self.state.borrow_mut().stroke_style = + CanvasFillOrStrokeStyle::Pattern(Dom::from_ref(&*pattern)); + if !pattern.origin_is_clean() { + self.set_origin_unclean(); + } + }, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle + pub fn fill_style(&self) -> StringOrCanvasGradientOrCanvasPattern { + match self.state.borrow().fill_style { + CanvasFillOrStrokeStyle::Color(ref rgba) => { + let mut result = String::new(); + serialize(rgba, &mut result).unwrap(); + StringOrCanvasGradientOrCanvasPattern::String(DOMString::from(result)) + }, + CanvasFillOrStrokeStyle::Gradient(ref gradient) => { + StringOrCanvasGradientOrCanvasPattern::CanvasGradient(DomRoot::from_ref(&*gradient)) + }, + CanvasFillOrStrokeStyle::Pattern(ref pattern) => { + StringOrCanvasGradientOrCanvasPattern::CanvasPattern(DomRoot::from_ref(&*pattern)) + }, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle + pub fn set_fill_style( + &self, + canvas: Option<&HTMLCanvasElement>, + value: StringOrCanvasGradientOrCanvasPattern, + ) { + match value { + StringOrCanvasGradientOrCanvasPattern::String(string) => { + if let Ok(rgba) = self.parse_color(canvas, &string) { + self.state.borrow_mut().fill_style = CanvasFillOrStrokeStyle::Color(rgba); + } + }, + StringOrCanvasGradientOrCanvasPattern::CanvasGradient(gradient) => { + self.state.borrow_mut().fill_style = + CanvasFillOrStrokeStyle::Gradient(Dom::from_ref(&*gradient)); + }, + StringOrCanvasGradientOrCanvasPattern::CanvasPattern(pattern) => { + self.state.borrow_mut().fill_style = + CanvasFillOrStrokeStyle::Pattern(Dom::from_ref(&*pattern)); + if !pattern.origin_is_clean() { + self.set_origin_unclean(); + } + }, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-createlineargradient + pub fn create_linear_gradient( + &self, + global: &GlobalScope, + x0: Finite<f64>, + y0: Finite<f64>, + x1: Finite<f64>, + y1: Finite<f64>, + ) -> DomRoot<CanvasGradient> { + CanvasGradient::new( + global, + CanvasGradientStyle::Linear(LinearGradientStyle::new(*x0, *y0, *x1, *y1, Vec::new())), + ) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-createradialgradient + pub fn create_radial_gradient( + &self, + global: &GlobalScope, + x0: Finite<f64>, + y0: Finite<f64>, + r0: Finite<f64>, + x1: Finite<f64>, + y1: Finite<f64>, + r1: Finite<f64>, + ) -> Fallible<DomRoot<CanvasGradient>> { + if *r0 < 0. || *r1 < 0. { + return Err(Error::IndexSize); + } + + Ok(CanvasGradient::new( + global, + CanvasGradientStyle::Radial(RadialGradientStyle::new( + *x0, + *y0, + *r0, + *x1, + *y1, + *r1, + Vec::new(), + )), + )) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-createpattern + pub fn create_pattern( + &self, + global: &GlobalScope, + image: CanvasImageSource, + mut repetition: DOMString, + ) -> Fallible<Option<DomRoot<CanvasPattern>>> { + let (image_data, image_size) = match image { + CanvasImageSource::HTMLImageElement(ref image) => { + // https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument + if !image.is_usable()? { + return Ok(None); + } + + image + .get_url() + .and_then(|url| { + self.fetch_image_data(url, cors_setting_for_element(image.upcast())) + }) + .ok_or(Error::InvalidState)? + }, + CanvasImageSource::HTMLCanvasElement(ref canvas) => { + let (data, size) = canvas.fetch_all_data().ok_or(Error::InvalidState)?; + let data = data + .map(|data| data.to_vec()) + .unwrap_or_else(|| vec![0; size.area() as usize * 4]); + (data, size) + }, + CanvasImageSource::OffscreenCanvas(ref canvas) => { + let (data, size) = canvas.fetch_all_data().ok_or(Error::InvalidState)?; + let data = data + .map(|data| data.to_vec()) + .unwrap_or_else(|| vec![0; size.area() as usize * 4]); + (data, size) + }, + CanvasImageSource::CSSStyleValue(ref value) => value + .get_url(self.base_url.clone()) + .and_then(|url| self.fetch_image_data(url, None)) + .ok_or(Error::InvalidState)?, + }; + + if repetition.is_empty() { + repetition.push_str("repeat"); + } + + if let Ok(rep) = RepetitionStyle::from_str(&repetition) { + Ok(Some(CanvasPattern::new( + global, + image_data, + image_size, + rep, + self.is_origin_clean(image), + ))) + } else { + Err(Error::Syntax) + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-save + pub fn save(&self) { + self.saved_states + .borrow_mut() + .push(self.state.borrow().clone()); + self.send_canvas_2d_msg(Canvas2dMsg::SaveContext); + } + + #[allow(unrooted_must_root)] + // https://html.spec.whatwg.org/multipage/#dom-context-2d-restore + pub fn restore(&self) { + let mut saved_states = self.saved_states.borrow_mut(); + if let Some(state) = saved_states.pop() { + self.state.borrow_mut().clone_from(&state); + self.send_canvas_2d_msg(Canvas2dMsg::RestoreContext); + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalalpha + pub fn global_alpha(&self) -> f64 { + self.state.borrow().global_alpha + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalalpha + pub fn set_global_alpha(&self, alpha: f64) { + if !alpha.is_finite() || alpha > 1.0 || alpha < 0.0 { + return; + } + + self.state.borrow_mut().global_alpha = alpha; + self.send_canvas_2d_msg(Canvas2dMsg::SetGlobalAlpha(alpha as f32)) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation + pub fn global_composite_operation(&self) -> DOMString { + match self.state.borrow().global_composition { + CompositionOrBlending::Composition(op) => DOMString::from(op.to_str()), + CompositionOrBlending::Blending(op) => DOMString::from(op.to_str()), + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation + pub fn set_global_composite_operation(&self, op_str: DOMString) { + if let Ok(op) = CompositionOrBlending::from_str(&op_str) { + self.state.borrow_mut().global_composition = op; + self.send_canvas_2d_msg(Canvas2dMsg::SetGlobalComposition(op)) + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-imagesmoothingenabled + pub fn image_smoothing_enabled(&self) -> bool { + self.state.borrow().image_smoothing_enabled + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-imagesmoothingenabled + pub fn set_image_smoothing_enabled(&self, value: bool) { + self.state.borrow_mut().image_smoothing_enabled = value; + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-filltext + pub fn fill_text( + &self, + canvas: Option<&HTMLCanvasElement>, + text: DOMString, + x: f64, + y: f64, + max_width: Option<f64>, + ) { + if !x.is_finite() || !y.is_finite() { + return; + } + if max_width.map_or(false, |max_width| !max_width.is_finite() || max_width <= 0.) { + return; + } + if self.state.borrow().font_style.is_none() { + self.set_font(canvas, CanvasContextState::DEFAULT_FONT_STYLE.into()) + } + + let is_rtl = match self.state.borrow().direction { + Direction::Ltr => false, + Direction::Rtl => true, + Direction::Inherit => false, // TODO: resolve direction wrt to canvas element + }; + + let style = self.state.borrow().fill_style.to_fill_or_stroke_style(); + self.send_canvas_2d_msg(Canvas2dMsg::FillText( + text.into(), + x, + y, + max_width, + style, + is_rtl, + )); + } + + // https://html.spec.whatwg.org/multipage/#textmetrics + pub fn measure_text(&self, global: &GlobalScope, _text: DOMString) -> DomRoot<TextMetrics> { + // FIXME: for now faking the implementation of MeasureText(). + // See https://github.com/servo/servo/issues/5411#issuecomment-533776291 + TextMetrics::new( + global, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + ) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-font + pub fn set_font(&self, canvas: Option<&HTMLCanvasElement>, value: DOMString) { + let canvas = match canvas { + Some(element) => element, + None => return, // offscreen canvas doesn't have a placeholder canvas + }; + let node = canvas.upcast::<Node>(); + let window = window_from_node(&*canvas); + let resolved_font_style = match window.resolved_font_style_query(&node, value.to_string()) { + Some(value) => value, + None => return, // syntax error + }; + self.state.borrow_mut().font_style = Some((*resolved_font_style).clone()); + self.send_canvas_2d_msg(Canvas2dMsg::SetFont((*resolved_font_style).clone())); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-font + pub fn font(&self) -> DOMString { + self.state.borrow().font_style.as_ref().map_or_else( + || CanvasContextState::DEFAULT_FONT_STYLE.into(), + |style| { + let mut result = String::new(); + serialize_font(style, &mut result).unwrap(); + DOMString::from(result) + }, + ) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign + pub fn text_align(&self) -> CanvasTextAlign { + match self.state.borrow().text_align { + TextAlign::Start => CanvasTextAlign::Start, + TextAlign::End => CanvasTextAlign::End, + TextAlign::Left => CanvasTextAlign::Left, + TextAlign::Right => CanvasTextAlign::Right, + TextAlign::Center => CanvasTextAlign::Center, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign + pub fn set_text_align(&self, value: CanvasTextAlign) { + let text_align = match value { + CanvasTextAlign::Start => TextAlign::Start, + CanvasTextAlign::End => TextAlign::End, + CanvasTextAlign::Left => TextAlign::Left, + CanvasTextAlign::Right => TextAlign::Right, + CanvasTextAlign::Center => TextAlign::Center, + }; + self.state.borrow_mut().text_align = text_align; + self.send_canvas_2d_msg(Canvas2dMsg::SetTextAlign(text_align)); + } + + pub fn text_baseline(&self) -> CanvasTextBaseline { + match self.state.borrow().text_baseline { + TextBaseline::Top => CanvasTextBaseline::Top, + TextBaseline::Hanging => CanvasTextBaseline::Hanging, + TextBaseline::Middle => CanvasTextBaseline::Middle, + TextBaseline::Alphabetic => CanvasTextBaseline::Alphabetic, + TextBaseline::Ideographic => CanvasTextBaseline::Ideographic, + TextBaseline::Bottom => CanvasTextBaseline::Bottom, + } + } + + pub fn set_text_baseline(&self, value: CanvasTextBaseline) { + let text_baseline = match value { + CanvasTextBaseline::Top => TextBaseline::Top, + CanvasTextBaseline::Hanging => TextBaseline::Hanging, + CanvasTextBaseline::Middle => TextBaseline::Middle, + CanvasTextBaseline::Alphabetic => TextBaseline::Alphabetic, + CanvasTextBaseline::Ideographic => TextBaseline::Ideographic, + CanvasTextBaseline::Bottom => TextBaseline::Bottom, + }; + self.state.borrow_mut().text_baseline = text_baseline; + self.send_canvas_2d_msg(Canvas2dMsg::SetTextBaseline(text_baseline)); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction + pub fn direction(&self) -> CanvasDirection { + match self.state.borrow().direction { + Direction::Ltr => CanvasDirection::Ltr, + Direction::Rtl => CanvasDirection::Rtl, + Direction::Inherit => CanvasDirection::Inherit, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction + pub fn set_direction(&self, value: CanvasDirection) { + let direction = match value { + CanvasDirection::Ltr => Direction::Ltr, + CanvasDirection::Rtl => Direction::Rtl, + CanvasDirection::Inherit => Direction::Inherit, + }; + self.state.borrow_mut().direction = direction; + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth + pub fn line_width(&self) -> f64 { + self.state.borrow().line_width + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth + pub fn set_line_width(&self, width: f64) { + if !width.is_finite() || width <= 0.0 { + return; + } + + self.state.borrow_mut().line_width = width; + self.send_canvas_2d_msg(Canvas2dMsg::SetLineWidth(width as f32)) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap + pub fn line_cap(&self) -> CanvasLineCap { + match self.state.borrow().line_cap { + LineCapStyle::Butt => CanvasLineCap::Butt, + LineCapStyle::Round => CanvasLineCap::Round, + LineCapStyle::Square => CanvasLineCap::Square, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap + pub fn set_line_cap(&self, cap: CanvasLineCap) { + let line_cap = match cap { + CanvasLineCap::Butt => LineCapStyle::Butt, + CanvasLineCap::Round => LineCapStyle::Round, + CanvasLineCap::Square => LineCapStyle::Square, + }; + self.state.borrow_mut().line_cap = line_cap; + self.send_canvas_2d_msg(Canvas2dMsg::SetLineCap(line_cap)); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin + pub fn line_join(&self) -> CanvasLineJoin { + match self.state.borrow().line_join { + LineJoinStyle::Round => CanvasLineJoin::Round, + LineJoinStyle::Bevel => CanvasLineJoin::Bevel, + LineJoinStyle::Miter => CanvasLineJoin::Miter, + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin + pub fn set_line_join(&self, join: CanvasLineJoin) { + let line_join = match join { + CanvasLineJoin::Round => LineJoinStyle::Round, + CanvasLineJoin::Bevel => LineJoinStyle::Bevel, + CanvasLineJoin::Miter => LineJoinStyle::Miter, + }; + self.state.borrow_mut().line_join = line_join; + self.send_canvas_2d_msg(Canvas2dMsg::SetLineJoin(line_join)); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit + pub fn miter_limit(&self) -> f64 { + self.state.borrow().miter_limit + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit + pub fn set_miter_limit(&self, limit: f64) { + if !limit.is_finite() || limit <= 0.0 { + return; + } + + self.state.borrow_mut().miter_limit = limit; + self.send_canvas_2d_msg(Canvas2dMsg::SetMiterLimit(limit as f32)) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-createimagedata + pub fn create_image_data( + &self, + global: &GlobalScope, + sw: i32, + sh: i32, + ) -> Fallible<DomRoot<ImageData>> { + if sw == 0 || sh == 0 { + return Err(Error::IndexSize); + } + ImageData::new(global, sw.abs() as u32, sh.abs() as u32, None) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-createimagedata + pub fn create_image_data_( + &self, + global: &GlobalScope, + imagedata: &ImageData, + ) -> Fallible<DomRoot<ImageData>> { + ImageData::new(global, imagedata.Width(), imagedata.Height(), None) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-getimagedata + pub fn get_image_data( + &self, + canvas_size: Size2D<u64>, + global: &GlobalScope, + sx: i32, + sy: i32, + sw: i32, + sh: i32, + ) -> Fallible<DomRoot<ImageData>> { + // FIXME(nox): There are many arithmetic operations here that can + // overflow or underflow, this should probably be audited. + + if sw == 0 || sh == 0 { + return Err(Error::IndexSize); + } + + if !self.origin_is_clean() { + return Err(Error::Security); + } + + let (origin, size) = adjust_size_sign(Point2D::new(sx, sy), Size2D::new(sw, sh)); + let read_rect = match pixels::clip(origin, size.to_u64(), canvas_size) { + Some(rect) => rect, + None => { + // All the pixels are outside the canvas surface. + return ImageData::new(global, size.width, size.height, None); + }, + }; + + ImageData::new( + global, + size.width, + size.height, + Some(self.get_rect(canvas_size, read_rect)), + ) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata + pub fn put_image_data( + &self, + canvas_size: Size2D<u64>, + imagedata: &ImageData, + dx: i32, + dy: i32, + ) { + self.put_image_data_( + canvas_size, + imagedata, + dx, + dy, + 0, + 0, + imagedata.Width() as i32, + imagedata.Height() as i32, + ) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata + #[allow(unsafe_code)] + pub fn put_image_data_( + &self, + canvas_size: Size2D<u64>, + imagedata: &ImageData, + dx: i32, + dy: i32, + dirty_x: i32, + dirty_y: i32, + dirty_width: i32, + dirty_height: i32, + ) { + // FIXME(nox): There are many arithmetic operations here that can + // overflow or underflow, this should probably be audited. + + let imagedata_size = Size2D::new(imagedata.Width(), imagedata.Height()); + if imagedata_size.area() == 0 { + return; + } + + // Step 1. + // Done later. + + // Step 2. + // TODO: throw InvalidState if buffer is detached. + + // Steps 3-6. + let (src_origin, src_size) = adjust_size_sign( + Point2D::new(dirty_x, dirty_y), + Size2D::new(dirty_width, dirty_height), + ); + let src_rect = match pixels::clip(src_origin, src_size.to_u64(), imagedata_size.to_u64()) { + Some(rect) => rect, + None => return, + }; + let (dst_origin, _) = adjust_size_sign( + Point2D::new(dirty_x.saturating_add(dx), dirty_y.saturating_add(dy)), + Size2D::new(dirty_width, dirty_height), + ); + // By clipping to the canvas surface, we avoid sending any pixel + // that would fall outside it. + let dst_rect = match pixels::clip(dst_origin, src_rect.size, canvas_size) { + Some(rect) => rect, + None => return, + }; + + // Step 7. + let (sender, receiver) = ipc::bytes_channel().unwrap(); + let pixels = unsafe { &imagedata.get_rect(Rect::new(src_rect.origin, dst_rect.size)) }; + self.send_canvas_2d_msg(Canvas2dMsg::PutImageData(dst_rect, receiver)); + sender.send(pixels).unwrap(); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage + pub fn draw_image( + &self, + canvas: Option<&HTMLCanvasElement>, + image: CanvasImageSource, + dx: f64, + dy: f64, + ) -> ErrorResult { + if !(dx.is_finite() && dy.is_finite()) { + return Ok(()); + } + + self.draw_image_internal(canvas, image, 0f64, 0f64, None, None, dx, dy, None, None) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage + pub fn draw_image_( + &self, + canvas: Option<&HTMLCanvasElement>, + image: CanvasImageSource, + dx: f64, + dy: f64, + dw: f64, + dh: f64, + ) -> ErrorResult { + if !(dx.is_finite() && dy.is_finite() && dw.is_finite() && dh.is_finite()) { + return Ok(()); + } + + self.draw_image_internal( + canvas, + image, + 0f64, + 0f64, + None, + None, + dx, + dy, + Some(dw), + Some(dh), + ) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage + pub fn draw_image__( + &self, + canvas: Option<&HTMLCanvasElement>, + image: CanvasImageSource, + sx: f64, + sy: f64, + sw: f64, + sh: f64, + dx: f64, + dy: f64, + dw: f64, + dh: f64, + ) -> ErrorResult { + if !(sx.is_finite() && + sy.is_finite() && + sw.is_finite() && + sh.is_finite() && + dx.is_finite() && + dy.is_finite() && + dw.is_finite() && + dh.is_finite()) + { + return Ok(()); + } + + self.draw_image_internal( + canvas, + image, + sx, + sy, + Some(sw), + Some(sh), + dx, + dy, + Some(dw), + Some(dh), + ) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-beginpath + pub fn begin_path(&self) { + self.send_canvas_2d_msg(Canvas2dMsg::BeginPath); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-fill + pub fn fill(&self, _fill_rule: CanvasFillRule) { + // TODO: Process fill rule + let style = self.state.borrow().fill_style.to_fill_or_stroke_style(); + self.send_canvas_2d_msg(Canvas2dMsg::Fill(style)); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke + pub fn stroke(&self) { + let style = self.state.borrow().stroke_style.to_fill_or_stroke_style(); + self.send_canvas_2d_msg(Canvas2dMsg::Stroke(style)); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip + pub fn clip(&self, _fill_rule: CanvasFillRule) { + // TODO: Process fill rule + self.send_canvas_2d_msg(Canvas2dMsg::Clip); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath + pub fn is_point_in_path( + &self, + global: &GlobalScope, + x: f64, + y: f64, + fill_rule: CanvasFillRule, + ) -> bool { + if !(x.is_finite() && y.is_finite()) { + return false; + } + + let fill_rule = match fill_rule { + CanvasFillRule::Nonzero => FillRule::Nonzero, + CanvasFillRule::Evenodd => FillRule::Evenodd, + }; + let (sender, receiver) = + profiled_ipc::channel::<bool>(global.time_profiler_chan().clone()).unwrap(); + self.send_canvas_2d_msg(Canvas2dMsg::IsPointInPath(x, y, fill_rule, sender)); + receiver.recv().unwrap() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-scale + pub fn scale(&self, x: f64, y: f64) { + if !(x.is_finite() && y.is_finite()) { + return; + } + + let transform = self.state.borrow().transform; + self.state.borrow_mut().transform = transform.pre_scale(x as f32, y as f32); + self.update_transform() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-rotate + pub fn rotate(&self, angle: f64) { + if angle == 0.0 || !angle.is_finite() { + return; + } + + let (sin, cos) = (angle.sin(), angle.cos()); + let transform = self.state.borrow().transform; + self.state.borrow_mut().transform = transform.pre_transform(&Transform2D::row_major( + cos as f32, + sin as f32, + -sin as f32, + cos as f32, + 0.0, + 0.0, + )); + self.update_transform() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-translate + pub fn translate(&self, x: f64, y: f64) { + if !(x.is_finite() && y.is_finite()) { + return; + } + + let transform = self.state.borrow().transform; + self.state.borrow_mut().transform = transform.pre_translate(vec2(x as f32, y as f32)); + self.update_transform() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-transform + pub fn transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) { + if !(a.is_finite() && + b.is_finite() && + c.is_finite() && + d.is_finite() && + e.is_finite() && + f.is_finite()) + { + return; + } + + let transform = self.state.borrow().transform; + self.state.borrow_mut().transform = transform.pre_transform(&Transform2D::row_major( + a as f32, b as f32, c as f32, d as f32, e as f32, f as f32, + )); + self.update_transform() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-gettransform + pub fn get_transform(&self, global: &GlobalScope) -> DomRoot<DOMMatrix> { + let (sender, receiver) = ipc::channel::<Transform2D<f32>>().unwrap(); + self.send_canvas_2d_msg(Canvas2dMsg::GetTransform(sender)); + let transform = receiver.recv().unwrap(); + + DOMMatrix::new(global, true, transform.cast::<f64>().to_3d()) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-settransform + pub fn set_transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) { + if !(a.is_finite() && + b.is_finite() && + c.is_finite() && + d.is_finite() && + e.is_finite() && + f.is_finite()) + { + return; + } + + self.state.borrow_mut().transform = + Transform2D::row_major(a as f32, b as f32, c as f32, d as f32, e as f32, f as f32); + self.update_transform() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-resettransform + pub fn reset_transform(&self) { + self.state.borrow_mut().transform = Transform2D::identity(); + self.update_transform() + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath + pub fn close_path(&self) { + self.send_canvas_2d_msg(Canvas2dMsg::ClosePath); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto + pub fn move_to(&self, x: f64, y: f64) { + if !(x.is_finite() && y.is_finite()) { + return; + } + self.send_canvas_2d_msg(Canvas2dMsg::MoveTo(Point2D::new(x as f32, y as f32))); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-lineto + pub fn line_to(&self, x: f64, y: f64) { + if !(x.is_finite() && y.is_finite()) { + return; + } + self.send_canvas_2d_msg(Canvas2dMsg::LineTo(Point2D::new(x as f32, y as f32))); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-rect + pub fn rect(&self, x: f64, y: f64, width: f64, height: f64) { + if [x, y, width, height].iter().all(|val| val.is_finite()) { + let rect = Rect::new( + Point2D::new(x as f32, y as f32), + Size2D::new(width as f32, height as f32), + ); + self.send_canvas_2d_msg(Canvas2dMsg::Rect(rect)); + } + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto + pub fn quadratic_curve_to(&self, cpx: f64, cpy: f64, x: f64, y: f64) { + if !(cpx.is_finite() && cpy.is_finite() && x.is_finite() && y.is_finite()) { + return; + } + self.send_canvas_2d_msg(Canvas2dMsg::QuadraticCurveTo( + Point2D::new(cpx as f32, cpy as f32), + Point2D::new(x as f32, y as f32), + )); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-beziercurveto + pub fn bezier_curve_to(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) { + if !(cp1x.is_finite() && + cp1y.is_finite() && + cp2x.is_finite() && + cp2y.is_finite() && + x.is_finite() && + y.is_finite()) + { + return; + } + self.send_canvas_2d_msg(Canvas2dMsg::BezierCurveTo( + Point2D::new(cp1x as f32, cp1y as f32), + Point2D::new(cp2x as f32, cp2y as f32), + Point2D::new(x as f32, y as f32), + )); + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-arc + pub fn arc(&self, x: f64, y: f64, r: f64, start: f64, end: f64, ccw: bool) -> ErrorResult { + if !([x, y, r, start, end].iter().all(|x| x.is_finite())) { + return Ok(()); + } + + if r < 0.0 { + return Err(Error::IndexSize); + } + + self.send_canvas_2d_msg(Canvas2dMsg::Arc( + Point2D::new(x as f32, y as f32), + r as f32, + start as f32, + end as f32, + ccw, + )); + Ok(()) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-arcto + pub fn arc_to(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, r: f64) -> ErrorResult { + if !([cp1x, cp1y, cp2x, cp2y, r].iter().all(|x| x.is_finite())) { + return Ok(()); + } + if r < 0.0 { + return Err(Error::IndexSize); + } + + self.send_canvas_2d_msg(Canvas2dMsg::ArcTo( + Point2D::new(cp1x as f32, cp1y as f32), + Point2D::new(cp2x as f32, cp2y as f32), + r as f32, + )); + Ok(()) + } + + // https://html.spec.whatwg.org/multipage/#dom-context-2d-ellipse + pub fn ellipse( + &self, + x: f64, + y: f64, + rx: f64, + ry: f64, + rotation: f64, + start: f64, + end: f64, + ccw: bool, + ) -> ErrorResult { + if !([x, y, rx, ry, rotation, start, end] + .iter() + .all(|x| x.is_finite())) + { + return Ok(()); + } + if rx < 0.0 || ry < 0.0 { + return Err(Error::IndexSize); + } + + self.send_canvas_2d_msg(Canvas2dMsg::Ellipse( + Point2D::new(x as f32, y as f32), + rx as f32, + ry as f32, + rotation as f32, + start as f32, + end as f32, + ccw, + )); + Ok(()) + } +} + +pub fn parse_color(string: &str) -> Result<RGBA, ()> { + let mut input = ParserInput::new(string); + let mut parser = Parser::new(&mut input); + match CSSColor::parse(&mut parser) { + Ok(CSSColor::RGBA(rgba)) => { + if parser.is_exhausted() { + Ok(rgba) + } else { + Err(()) + } + }, + _ => Err(()), + } +} + +// Used by drawImage to determine if a source or destination rectangle is valid +// Origin coordinates and size cannot be negative. Size has to be greater than zero +pub fn is_rect_valid(rect: Rect<f64>) -> bool { + rect.size.width > 0.0 && rect.size.height > 0.0 +} + +// https://html.spec.whatwg.org/multipage/#serialisation-of-a-color +pub fn serialize<W>(color: &RGBA, dest: &mut W) -> fmt::Result +where + W: fmt::Write, +{ + let red = color.red; + let green = color.green; + let blue = color.blue; + + if color.alpha == 255 { + write!( + dest, + "#{:x}{:x}{:x}{:x}{:x}{:x}", + red >> 4, + red & 0xF, + green >> 4, + green & 0xF, + blue >> 4, + blue & 0xF + ) + } else { + write!( + dest, + "rgba({}, {}, {}, {})", + red, + green, + blue, + color.alpha_f32() + ) + } +} + +pub fn adjust_size_sign( + mut origin: Point2D<i32>, + mut size: Size2D<i32>, +) -> (Point2D<i32>, Size2D<u32>) { + if size.width < 0 { + size.width = -size.width; + origin.x = origin.x.saturating_sub(size.width); + } + if size.height < 0 { + size.height = -size.height; + origin.y = origin.y.saturating_sub(size.height); + } + (origin, size.to_u32()) +} + +fn serialize_font<W>(style: &Font, dest: &mut W) -> fmt::Result +where + W: fmt::Write, +{ + if style.font_style == FontStyle::Italic { + write!(dest, "{} ", style.font_style.to_css_string())?; + } + if style.font_weight.is_bold() { + write!(dest, "{} ", style.font_weight.to_css_string())?; + } + if style.font_variant_caps == FontVariantCaps::SmallCaps { + write!(dest, "{} ", style.font_variant_caps.to_css_string())?; + } + write!( + dest, + "{} {}", + style.font_size.to_css_string(), + style.font_family.to_css_string() + ) +} |