From 8981183ec13ec61f6c1979342ba1ff60a027e861 Mon Sep 17 00:00:00 2001 From: Aalhad Date: Fri, 25 Jul 2014 11:53:47 -0400 Subject: [PATCH] servo: Merge #2920 - Implement very basic canvas rendering context logic. No visual display present (from Ms2ger:canvas) Source-Repo: https://github.com/servo/servo Source-Revision: 440b2a995ffe76aaff8e8080188e5614b830ae38 --- .../script/dom/canvasrenderingcontext2d.rs | 147 ++++++++++++++++++ .../script/dom/htmlcanvaselement.rs | 128 ++++++++++++++- .../components/script/dom/virtualmethods.rs | 12 +- .../webidls/CanvasRenderingContext2D.webidl | 104 +++++++++++++ .../dom/webidls/HTMLCanvasElement.webidl | 7 +- servo/src/components/script/script.rs | 2 + servo/src/test/content/test_interfaces.html | 1 + servo/src/test/html/test_canvas.html | 18 +++ 8 files changed, 411 insertions(+), 8 deletions(-) create mode 100644 servo/src/components/script/dom/canvasrenderingcontext2d.rs create mode 100644 servo/src/components/script/dom/webidls/CanvasRenderingContext2D.webidl create mode 100644 servo/src/test/html/test_canvas.html diff --git a/servo/src/components/script/dom/canvasrenderingcontext2d.rs b/servo/src/components/script/dom/canvasrenderingcontext2d.rs new file mode 100644 index 000000000000..42cfd21472b0 --- /dev/null +++ b/servo/src/components/script/dom/canvasrenderingcontext2d.rs @@ -0,0 +1,147 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding; +use dom::bindings::global::{GlobalRef, GlobalField}; +use dom::bindings::js::{JSRef, Temporary}; +use dom::bindings::trace::Untraceable; +use dom::bindings::utils::{Reflector, Reflectable, reflect_dom_object}; + +use azure::azure_hl::{DrawTarget, Color, B8G8R8A8, SkiaBackend, StrokeOptions, DrawOptions}; +use azure::azure_hl::ColorPattern; +use geom::point::Point2D; +use geom::rect::Rect; +use geom::size::Size2D; + +use std::comm; +use std::task::TaskBuilder; + +#[deriving(Encodable)] +pub struct CanvasRenderingContext2D { + reflector_: Reflector, + global: GlobalField, + renderer: Untraceable>, +} + +enum CanvasMsg { + FillRect(Rect), + ClearRect(Rect), + StrokeRect(Rect), + Recreate(Size2D), + Close, +} + +struct CanvasRenderTask { + drawtarget: DrawTarget, + fill_color: ColorPattern, + stroke_color: ColorPattern, + stroke_opts: StrokeOptions, +} + +impl CanvasRenderTask { + fn new(size: Size2D) -> CanvasRenderTask { + CanvasRenderTask { + drawtarget: CanvasRenderTask::create(size), + fill_color: ColorPattern::new(Color::new(0., 0., 0., 1.)), + stroke_color: ColorPattern::new(Color::new(0., 0., 0., 1.)), + stroke_opts: StrokeOptions::new(1.0, 1.0), + } + } + + fn start(size: Size2D) -> Sender { + let (chan, port) = comm::channel::(); + let builder = TaskBuilder::new().named("CanvasTask"); + builder.spawn(proc() { + let mut renderer = CanvasRenderTask::new(size); + + loop { + match port.recv() { + FillRect(ref rect) => renderer.fill_rect(rect), + StrokeRect(ref rect) => renderer.stroke_rect(rect), + ClearRect(ref rect) => renderer.clear_rect(rect), + Recreate(size) => renderer.recreate(size), + Close => break, + } + } + }); + chan + } + + fn fill_rect(&self, rect: &Rect) { + let drawopts = DrawOptions::new(1.0, 0); + self.drawtarget.fill_rect(rect, &self.fill_color, Some(&drawopts)); + } + + fn clear_rect(&self, rect: &Rect) { + self.drawtarget.clear_rect(rect); + } + + fn stroke_rect(&self, rect: &Rect) { + let drawopts = DrawOptions::new(1.0, 0); + self.drawtarget.stroke_rect(rect, &self.stroke_color, &self.stroke_opts, &drawopts); + } + + fn create(size: Size2D) -> DrawTarget { + DrawTarget::new(SkiaBackend, size, B8G8R8A8) + } + + fn recreate(&mut self, size: Size2D) { + self.drawtarget = CanvasRenderTask::create(size); + } +} + +impl CanvasRenderingContext2D { + pub fn new_inherited(global: &GlobalRef, size: Size2D) -> CanvasRenderingContext2D { + CanvasRenderingContext2D { + reflector_: Reflector::new(), + global: GlobalField::from_rooted(global), + renderer: Untraceable::new(CanvasRenderTask::start(size)), + } + } + + pub fn new(global: &GlobalRef, size: Size2D) -> Temporary { + reflect_dom_object(box CanvasRenderingContext2D::new_inherited(global, size), + global, CanvasRenderingContext2DBinding::Wrap) + } + + pub fn recreate(&self, size: Size2D) { + self.renderer.send(Recreate(size)); + } +} + +pub trait CanvasRenderingContext2DMethods { + fn FillRect(&self, x: f64, y: f64, width: f64, height: f64); + fn ClearRect(&self, x: f64, y: f64, width: f64, height: f64); + fn StrokeRect(&self, x: f64, y: f64, width: f64, height: f64); +} + +impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> { + fn FillRect(&self, x: f64, y: f64, width: f64, height: f64) { + let rect = Rect(Point2D(x as f32, y as f32), Size2D(width as f32, height as f32)); + self.renderer.send(FillRect(rect)); + } + + fn ClearRect(&self, x: f64, y: f64, width: f64, height: f64) { + let rect = Rect(Point2D(x as f32, y as f32), Size2D(width as f32, height as f32)); + self.renderer.send(ClearRect(rect)); + } + + fn StrokeRect(&self, x: f64, y: f64, width: f64, height: f64) { + let rect = Rect(Point2D(x as f32, y as f32), Size2D(width as f32, height as f32)); + self.renderer.send(StrokeRect(rect)); + } +} + +impl Reflectable for CanvasRenderingContext2D { + fn reflector<'a>(&'a self) -> &'a Reflector { + &self.reflector_ + } +} + +#[unsafe_destructor] +impl Drop for CanvasRenderingContext2D { + fn drop(&mut self) { + self.renderer.send(Close); + } +} diff --git a/servo/src/components/script/dom/htmlcanvaselement.rs b/servo/src/components/script/dom/htmlcanvaselement.rs index f9d884a9ab0b..e9d742cb62cb 100644 --- a/servo/src/components/script/dom/htmlcanvaselement.rs +++ b/servo/src/components/script/dom/htmlcanvaselement.rs @@ -4,18 +4,35 @@ use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding; use dom::bindings::codegen::InheritTypes::HTMLCanvasElementDerived; -use dom::bindings::js::{JSRef, Temporary}; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast}; +use dom::bindings::global::Window; +use dom::bindings::js::{JS, JSRef, Temporary, OptionalSettable}; +use dom::bindings::trace::Traceable; use dom::bindings::utils::{Reflectable, Reflector}; +use dom::canvasrenderingcontext2d::CanvasRenderingContext2D; use dom::document::Document; -use dom::element::HTMLCanvasElementTypeId; +use dom::element::{Element, HTMLCanvasElementTypeId, AttributeHandlers}; use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::htmlelement::HTMLElement; -use dom::node::{Node, ElementNodeTypeId}; +use dom::node::{Node, ElementNodeTypeId, window_from_node}; +use dom::virtualmethods::VirtualMethods; + use servo_util::str::DOMString; +use geom::size::Size2D; + +use std::cell::Cell; +use std::num; + +static DefaultWidth: u32 = 300; +static DefaultHeight: u32 = 150; + #[deriving(Encodable)] pub struct HTMLCanvasElement { pub htmlelement: HTMLElement, + context: Traceable>>>, + width: Traceable>, + height: Traceable>, } impl HTMLCanvasElementDerived for EventTarget { @@ -27,7 +44,10 @@ impl HTMLCanvasElementDerived for EventTarget { impl HTMLCanvasElement { pub fn new_inherited(localName: DOMString, document: &JSRef) -> HTMLCanvasElement { HTMLCanvasElement { - htmlelement: HTMLElement::new_inherited(HTMLCanvasElementTypeId, localName, document) + htmlelement: HTMLElement::new_inherited(HTMLCanvasElementTypeId, localName, document), + context: Traceable::new(Cell::new(None)), + width: Traceable::new(Cell::new(DefaultWidth)), + height: Traceable::new(Cell::new(DefaultHeight)), } } @@ -38,6 +58,106 @@ impl HTMLCanvasElement { } pub trait HTMLCanvasElementMethods { + fn Width(&self) -> u32; + fn SetWidth(&self, width: u32); + fn Height(&self) -> u32; + fn SetHeight(&self, height: u32); + fn GetContext(&self, id: DOMString) -> Option>; +} + +impl<'a> HTMLCanvasElementMethods for JSRef<'a, HTMLCanvasElement> { + fn Width(&self) -> u32 { + self.width.get() + } + + fn SetWidth(&self, width: u32) { + let elem: &JSRef = ElementCast::from_ref(self); + elem.set_uint_attribute("width", width) + } + + fn Height(&self) -> u32 { + self.height.get() + } + + fn SetHeight(&self, height: u32) { + let elem: &JSRef = ElementCast::from_ref(self); + elem.set_uint_attribute("height", height) + } + + fn GetContext(&self, id: DOMString) -> Option> { + if id.as_slice() != "2d" { + return None; + } + + if self.context.get().is_none() { + let window = window_from_node(self).root(); + let (w, h) = (self.width.get() as i32, self.height.get() as i32); + let context = CanvasRenderingContext2D::new(&Window(*window), Size2D(w, h)); + self.context.assign(Some(context)); + } + self.context.get().map(|context| Temporary::new(context)) + } +} + +impl<'a> VirtualMethods for JSRef<'a, HTMLCanvasElement> { + fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods+> { + let element: &JSRef = HTMLElementCast::from_ref(self); + Some(element as &VirtualMethods+) + } + + fn before_remove_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.before_remove_attr(name.clone(), value.clone()), + _ => (), + } + + let recreate = match name.as_slice() { + "width" => { + self.width.set(DefaultWidth); + true + } + "height" => { + self.height.set(DefaultHeight); + true + } + _ => false, + }; + + if recreate { + let (w, h) = (self.width.get() as i32, self.height.get() as i32); + match self.context.get() { + Some(ref context) => context.root().recreate(Size2D(w, h)), + None => () + } + } + } + + fn after_set_attr(&self, name: DOMString, value: DOMString) { + match self.super_type() { + Some(ref s) => s.after_set_attr(name.clone(), value.clone()), + _ => (), + } + + let recreate = match name.as_slice() { + "width" => { + self.width.set(num::from_str_radix(value.as_slice(), 10).unwrap()); + true + } + "height" => { + self.height.set(num::from_str_radix(value.as_slice(), 10).unwrap()); + true + } + _ => false, + }; + + if recreate { + let (w, h) = (self.width.get() as i32, self.height.get() as i32); + match self.context.get() { + Some(ref context) => context.root().recreate(Size2D(w, h)), + None => () + } + } + } } impl Reflectable for HTMLCanvasElement { diff --git a/servo/src/components/script/dom/virtualmethods.rs b/servo/src/components/script/dom/virtualmethods.rs index 9ebb9a336c47..04b750003c9d 100644 --- a/servo/src/components/script/dom/virtualmethods.rs +++ b/servo/src/components/script/dom/virtualmethods.rs @@ -6,6 +6,7 @@ use dom::attr::{AttrValue, StringAttrValue}; use dom::bindings::codegen::InheritTypes::ElementCast; use dom::bindings::codegen::InheritTypes::HTMLAnchorElementCast; use dom::bindings::codegen::InheritTypes::HTMLBodyElementCast; +use dom::bindings::codegen::InheritTypes::HTMLCanvasElementCast; use dom::bindings::codegen::InheritTypes::HTMLElementCast; use dom::bindings::codegen::InheritTypes::HTMLIFrameElementCast; use dom::bindings::codegen::InheritTypes::HTMLImageElementCast; @@ -13,11 +14,14 @@ use dom::bindings::codegen::InheritTypes::HTMLObjectElementCast; use dom::bindings::codegen::InheritTypes::HTMLStyleElementCast; use dom::bindings::js::JSRef; use dom::element::Element; -use dom::element::{ElementTypeId, HTMLAnchorElementTypeId, HTMLBodyElementTypeId, HTMLImageElementTypeId}; -use dom::element::{HTMLIFrameElementTypeId, HTMLObjectElementTypeId, HTMLStyleElementTypeId}; +use dom::element::{ElementTypeId, HTMLAnchorElementTypeId}; +use dom::element::{HTMLBodyElementTypeId, HTMLCanvasElementTypeId}; +use dom::element::{HTMLIFrameElementTypeId, HTMLImageElementTypeId}; +use dom::element::{HTMLObjectElementTypeId, HTMLStyleElementTypeId}; use dom::event::Event; use dom::htmlanchorelement::HTMLAnchorElement; use dom::htmlbodyelement::HTMLBodyElement; +use dom::htmlcanvaselement::HTMLCanvasElement; use dom::htmlelement::HTMLElement; use dom::htmliframeelement::HTMLIFrameElement; use dom::htmlimageelement::HTMLImageElement; @@ -111,6 +115,10 @@ pub fn vtable_for<'a>(node: &'a JSRef) -> &'a VirtualMethods+ { let element: &JSRef = HTMLBodyElementCast::to_ref(node).unwrap(); element as &VirtualMethods+ } + ElementNodeTypeId(HTMLCanvasElementTypeId) => { + let element: &JSRef = HTMLCanvasElementCast::to_ref(node).unwrap(); + element as &VirtualMethods+ + } ElementNodeTypeId(HTMLImageElementTypeId) => { let element: &JSRef = HTMLImageElementCast::to_ref(node).unwrap(); element as &VirtualMethods+ diff --git a/servo/src/components/script/dom/webidls/CanvasRenderingContext2D.webidl b/servo/src/components/script/dom/webidls/CanvasRenderingContext2D.webidl new file mode 100644 index 000000000000..a465afc97b1a --- /dev/null +++ b/servo/src/components/script/dom/webidls/CanvasRenderingContext2D.webidl @@ -0,0 +1,104 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 http://mozilla.org/MPL/2.0/. */ + +// http://www.whatwg.org/html/#2dcontext +//[Constructor(optional unsigned long width, unsigned long height), Exposed=Window,Worker] +interface CanvasRenderingContext2D { + + // back-reference to the canvas + //readonly attribute HTMLCanvasElement canvas; + + // canvas dimensions + // attribute unsigned long width; + // attribute unsigned long height; + + // for contexts that aren't directly fixed to a specific canvas + //void commit(); // push the image to the output bitmap + + // state + //void save(); // push state on state stack + //void restore(); // pop state stack and restore state + + // transformations (default transform is the identity matrix) + // attribute SVGMatrix currentTransform; + //void scale(unrestricted double x, unrestricted double y); + //void rotate(unrestricted double angle); + //void translate(unrestricted double x, unrestricted double y); + //void transform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f); + //void setTransform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f); + //void resetTransform(); + + // compositing + // attribute unrestricted double globalAlpha; // (default 1.0) + // attribute DOMString globalCompositeOperation; // (default source-over) + + // image smoothing + // attribute boolean imageSmoothingEnabled; // (default true) + + // colours and styles (see also the CanvasDrawingStyles interface) + // attribute (DOMString or CanvasGradient or CanvasPattern) strokeStyle; // (default black) + // attribute (DOMString or CanvasGradient or CanvasPattern) fillStyle; // (default black) + //CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1); + //CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1); + //CanvasPattern createPattern(CanvasImageSource image, [TreatNullAs=EmptyString] DOMString repetition); + + // shadows + // attribute unrestricted double shadowOffsetX; // (default 0) + // attribute unrestricted double shadowOffsetY; // (default 0) + // attribute unrestricted double shadowBlur; // (default 0) + // attribute DOMString shadowColor; // (default transparent black) + + // rects + //void clearRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); + //[LenientFloat] + void clearRect(double x, double y, double w, double h); + //void fillRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); + //[LenientFloat] + void fillRect(double x, double y, double w, double h); + //void strokeRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); + //[LenientFloat] + void strokeRect(double x, double y, double w, double h); + + // path API (see also CanvasPathMethods) + //void beginPath(); + //void fill(optional CanvasFillRule fillRule = "nonzero"); + //void fill(Path2D path, optional CanvasFillRule fillRule = "nonzero"); + //void stroke(); + //void stroke(Path2D path); + //void drawSystemFocusRing(Element element); + //void drawSystemFocusRing(Path2D path, Element element); + //boolean drawCustomFocusRing(Element element); + //boolean drawCustomFocusRing(Path2D path, Element element); + //void scrollPathIntoView(); + //void scrollPathIntoView(Path2D path); + //void clip(optional CanvasFillRule fillRule = "nonzero"); + //void clip(Path2D path, optional CanvasFillRule fillRule = "nonzero"); + //void resetClip(); + //boolean isPointInPath(unrestricted double x, unrestricted double y, optional CanvasFillRule fillRule = "nonzero"); + //boolean isPointInPath(Path2D path, unrestricted double x, unrestricted double y, optional CanvasFillRule fillRule = "nonzero"); + //boolean isPointInStroke(unrestricted double x, unrestricted double y); + //boolean isPointInStroke(Path2D path, unrestricted double x, unrestricted double y); + + // text (see also the CanvasDrawingStyles interface) + //void fillText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth); + //void strokeText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth); + //TextMetrics measureText(DOMString text); + + // drawing images + //void drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy); + //void drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy, unrestricted double dw, unrestricted double dh); + //void drawImage(CanvasImageSource image, unrestricted double sx, unrestricted double sy, unrestricted double sw, unrestricted double sh, unrestricted double dx, unrestricted double dy, unrestricted double dw, unrestricted double dh); + + // hit regions + //void addHitRegion(optional HitRegionOptions options); + //void removeHitRegion(DOMString id); + + // pixel manipulation + //ImageData createImageData(double sw, double sh); + //ImageData createImageData(ImageData imagedata); + //ImageData getImageData(double sx, double sy, double sw, double sh); + //void putImageData(ImageData imagedata, double dx, double dy); + //void putImageData(ImageData imagedata, double dx, double dy, double dirtyX, double dirtyY, double dirtyWidth, double dirtyHeight); +}; diff --git a/servo/src/components/script/dom/webidls/HTMLCanvasElement.webidl b/servo/src/components/script/dom/webidls/HTMLCanvasElement.webidl index 3e5f2a6e3a62..baff0dc745c0 100644 --- a/servo/src/components/script/dom/webidls/HTMLCanvasElement.webidl +++ b/servo/src/components/script/dom/webidls/HTMLCanvasElement.webidl @@ -7,10 +7,13 @@ //typedef (CanvasRenderingContext2D or WebGLRenderingContext) RenderingContext; interface HTMLCanvasElement : HTMLElement { - // attribute unsigned long width; - // attribute unsigned long height; + [Pure] + attribute unsigned long width; + [Pure] + attribute unsigned long height; //RenderingContext? getContext(DOMString contextId, any... arguments); + CanvasRenderingContext2D? getContext(DOMString contextId); //boolean probablySupportsContext(DOMString contextId, any... arguments); //void setContext(RenderingContext context); diff --git a/servo/src/components/script/script.rs b/servo/src/components/script/script.rs index 7da283836872..9019cfeb4dcd 100644 --- a/servo/src/components/script/script.rs +++ b/servo/src/components/script/script.rs @@ -19,6 +19,7 @@ #[phase(plugin, link)] extern crate log; +extern crate azure; extern crate debug; extern crate cssparser; extern crate collections; @@ -72,6 +73,7 @@ pub mod dom { pub mod attrlist; pub mod blob; pub mod browsercontext; + pub mod canvasrenderingcontext2d; pub mod characterdata; pub mod clientrect; pub mod clientrectlist; diff --git a/servo/src/test/content/test_interfaces.html b/servo/src/test/content/test_interfaces.html index dd64d84bb846..a5af62e377d5 100644 --- a/servo/src/test/content/test_interfaces.html +++ b/servo/src/test/content/test_interfaces.html @@ -53,6 +53,7 @@ var ecmaGlobals = [ var interfaceNamesInGlobalScope = [ "Attr", "Blob", + "CanvasRenderingContext2D", "CharacterData", "ClientRect", // #2814 "ClientRectList", // #2814 diff --git a/servo/src/test/html/test_canvas.html b/servo/src/test/html/test_canvas.html new file mode 100644 index 000000000000..8a9918af54cb --- /dev/null +++ b/servo/src/test/html/test_canvas.html @@ -0,0 +1,18 @@ + + + + + + +