/* 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(#[no_trace] RGBA), Gradient(Dom), Pattern(Dom), } 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, #[no_trace] global_composition: CompositionOrBlending, image_smoothing_enabled: bool, fill_style: CanvasFillOrStrokeStyle, stroke_style: CanvasFillOrStrokeStyle, line_width: f64, #[no_trace] line_cap: LineCapStyle, #[no_trace] line_join: LineJoinStyle, miter_limit: f64, #[no_trace] transform: Transform2D, shadow_offset_x: f64, shadow_offset_y: f64, shadow_blur: f64, #[no_trace] shadow_color: RGBA, #[no_trace] font_style: Option, #[no_trace] text_align: TextAlign, #[no_trace] text_baseline: TextBaseline, #[no_trace] 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"] #[no_trace] ipc_renderer: IpcSender, #[no_trace] canvas_id: CanvasId, state: DomRefCell, origin_clean: Cell, #[ignore_malloc_size_of = "Arc"] #[no_trace] image_cache: Arc, /// The base URL for resolving CSS image URL values. /// Needed because of https://github.com/servo/servo/issues/17625 #[no_trace] base_url: ServoUrl, #[no_trace] origin: ImmutableOrigin, /// Any missing image URLs. #[no_trace] missing_image_urls: DomRefCell>, saved_states: DomRefCell>, } impl CanvasState { pub(crate) fn new(global: &GlobalScope, size: Size2D) -> 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::() { 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 { &self.ipc_renderer } pub fn get_missing_image_urls(&self) -> &DomRefCell> { &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) { 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> { 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, ) -> Option<(Vec, Size2D)> { 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, ) -> 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 { 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::(); 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, rect: Rect) -> Vec { 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, sh: Option, dx: f64, dy: f64, dw: Option, dh: Option, ) -> 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, sh: Option, dx: f64, dy: f64, dw: Option, dh: Option, ) -> 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, sh: Option, dx: f64, dy: f64, dw: Option, dh: Option, ) -> 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, sx: f64, sy: f64, sw: Option, sh: Option, dx: f64, dy: f64, dw: Option, dh: Option, ) -> 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::().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, sx: f64, sy: f64, sw: f64, sh: f64, dx: f64, dy: f64, dw: f64, dh: f64, ) -> (Rect, Rect) { 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, y0: Finite, x1: Finite, y1: Finite, ) -> DomRoot { 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, y0: Finite, r0: Finite, x1: Finite, y1: Finite, r1: Finite, ) -> Fallible> { 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>> { 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, ) { 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 { // 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::(); 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> { 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> { 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, global: &GlobalScope, sx: i32, sy: i32, sw: i32, sh: i32, ) -> Fallible> { // 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, 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, 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::(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 = Transform2D::new(cos as f32, sin as f32, -sin as f32, cos as f32, 0.0, 0.0) .then(&transform); 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 = Transform2D::new(a as f32, b as f32, c as f32, d as f32, e as f32, f as f32) .then(&transform); self.update_transform() } // https://html.spec.whatwg.org/multipage/#dom-context-2d-gettransform pub fn get_transform(&self, global: &GlobalScope) -> DomRoot { let (sender, receiver) = ipc::channel::>().unwrap(); self.send_canvas_2d_msg(Canvas2dMsg::GetTransform(sender)); let transform = receiver.recv().unwrap(); DOMMatrix::new(global, true, transform.cast::().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::new(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 { 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) -> bool { rect.size.width > 0.0 && rect.size.height > 0.0 } // https://html.spec.whatwg.org/multipage/#serialisation-of-a-color pub fn serialize(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, mut size: Size2D, ) -> (Point2D, Size2D) { 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(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() ) }