Everywhere: Hoist the Libraries folder to the top-level

This commit is contained in:
Timothy Flynn 2024-11-09 12:25:08 -05:00 committed by Andreas Kling
commit 93712b24bf
Notes: github-actions[bot] 2024-11-10 11:51:52 +00:00
4547 changed files with 104 additions and 113 deletions

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/HTML/ImageData.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvascompositing
class CanvasCompositing {
public:
virtual ~CanvasCompositing() = default;
virtual float global_alpha() const = 0;
virtual void set_global_alpha(float) = 0;
protected:
CanvasCompositing() = default;
};
}

View file

@ -0,0 +1,6 @@
// https://html.spec.whatwg.org/multipage/canvas.html#canvascompositing
interface mixin CanvasCompositing {
// compositing
attribute unrestricted double globalAlpha; // (default 1.0)
[FIXME] attribute DOMString globalCompositeOperation; // (default "source-over")
};

View file

@ -0,0 +1,92 @@
/*
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/HTML/Canvas/CanvasDrawImage.h>
#include <LibWeb/HTML/ImageBitmap.h>
#include <LibWeb/SVG/SVGImageElement.h>
namespace Web::HTML {
static void default_source_size(CanvasImageSource const& image, float& source_width, float& source_height)
{
image.visit(
[&source_width, &source_height](JS::Handle<HTMLImageElement> const& source) {
if (source->immutable_bitmap()) {
source_width = source->immutable_bitmap()->width();
source_height = source->immutable_bitmap()->height();
} else {
// FIXME: This is very janky and not correct.
source_width = source->width();
source_height = source->height();
}
},
[&source_width, &source_height](JS::Handle<SVG::SVGImageElement> const& source) {
if (source->current_image_bitmap()) {
source_width = source->current_image_bitmap()->width();
source_height = source->current_image_bitmap()->height();
} else {
// FIXME: This is very janky and not correct.
source_width = source->width()->anim_val()->value();
source_height = source->height()->anim_val()->value();
}
},
[&source_width, &source_height](JS::Handle<HTML::HTMLVideoElement> const& source) {
if (auto const bitmap = source->bitmap(); bitmap) {
source_width = bitmap->width();
source_height = bitmap->height();
} else {
source_width = source->video_width();
source_height = source->video_height();
}
},
[&source_width, &source_height](JS::Handle<HTMLCanvasElement> const& source) {
if (source->surface()) {
source_width = source->surface()->size().width();
source_height = source->surface()->size().height();
} else {
source_width = source->width();
source_height = source->height();
}
},
[&source_width, &source_height](auto const& source) {
if (source->bitmap()) {
source_width = source->bitmap()->width();
source_height = source->bitmap()->height();
} else {
source_width = source->width();
source_height = source->height();
}
});
}
WebIDL::ExceptionOr<void> CanvasDrawImage::draw_image(Web::HTML::CanvasImageSource const& image, float destination_x, float destination_y)
{
// If not specified, the dw and dh arguments must default to the values of sw and sh, interpreted such that one CSS pixel in the image is treated as one unit in the output bitmap's coordinate space.
// If the sx, sy, sw, and sh arguments are omitted, then they must default to 0, 0, the image's intrinsic width in image pixels, and the image's intrinsic height in image pixels, respectively.
// If the image has no intrinsic dimensions, then the concrete object size must be used instead, as determined using the CSS "Concrete Object Size Resolution" algorithm, with the specified size having
// neither a definite width nor height, nor any additional constraints, the object's intrinsic properties being those of the image argument, and the default object size being the size of the output bitmap.
float source_width;
float source_height;
default_source_size(image, source_width, source_height);
return draw_image_internal(image, 0, 0, source_width, source_height, destination_x, destination_y, source_width, source_height);
}
WebIDL::ExceptionOr<void> CanvasDrawImage::draw_image(Web::HTML::CanvasImageSource const& image, float destination_x, float destination_y, float destination_width, float destination_height)
{
// If the sx, sy, sw, and sh arguments are omitted, then they must default to 0, 0, the image's intrinsic width in image pixels, and the image's intrinsic height in image pixels, respectively.
// If the image has no intrinsic dimensions, then the concrete object size must be used instead, as determined using the CSS "Concrete Object Size Resolution" algorithm, with the specified size having
// neither a definite width nor height, nor any additional constraints, the object's intrinsic properties being those of the image argument, and the default object size being the size of the output bitmap.
float source_width;
float source_height;
default_source_size(image, source_width, source_height);
return draw_image_internal(image, 0, 0, source_width, source_height, destination_x, destination_y, destination_width, destination_height);
}
WebIDL::ExceptionOr<void> CanvasDrawImage::draw_image(Web::HTML::CanvasImageSource const& image, float source_x, float source_y, float source_width, float source_height, float destination_x, float destination_y, float destination_width, float destination_height)
{
return draw_image_internal(image, source_x, source_y, source_width, source_height, destination_x, destination_y, destination_width, destination_height);
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/HTMLCanvasElement.h>
#include <LibWeb/HTML/HTMLImageElement.h>
#include <LibWeb/HTML/HTMLVideoElement.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvasimagesource
// NOTE: This is the Variant created by the IDL wrapper generator, and needs to be updated accordingly.
using CanvasImageSource = Variant<JS::Handle<HTMLImageElement>, JS::Handle<SVG::SVGImageElement>, JS::Handle<HTMLCanvasElement>, JS::Handle<ImageBitmap>, JS::Handle<HTMLVideoElement>>;
// https://html.spec.whatwg.org/multipage/canvas.html#canvasdrawimage
class CanvasDrawImage {
public:
virtual ~CanvasDrawImage() = default;
WebIDL::ExceptionOr<void> draw_image(CanvasImageSource const&, float destination_x, float destination_y);
WebIDL::ExceptionOr<void> draw_image(CanvasImageSource const&, float destination_x, float destination_y, float destination_width, float destination_height);
WebIDL::ExceptionOr<void> draw_image(CanvasImageSource const&, float source_x, float source_y, float source_width, float source_height, float destination_x, float destination_y, float destination_width, float destination_height);
virtual WebIDL::ExceptionOr<void> draw_image_internal(CanvasImageSource const&, float source_x, float source_y, float source_width, float source_height, float destination_x, float destination_y, float destination_width, float destination_height) = 0;
protected:
CanvasDrawImage() = default;
};
}

View file

@ -0,0 +1,22 @@
#import <HTML/HTMLCanvasElement.idl>
#import <HTML/HTMLImageElement.idl>
#import <HTML/HTMLVideoElement.idl>
#import <HTML/ImageBitmap.idl>
#import <SVG/SVGImageElement.idl>
typedef (HTMLImageElement or
SVGImageElement or
// FIXME: We should use HTMLOrSVGImageElement instead of HTMLImageElement
HTMLVideoElement or
HTMLCanvasElement or
ImageBitmap
// FIXME: OffscreenCanvas
// FIXME: VideoFrame
) CanvasImageSource;
// https://html.spec.whatwg.org/multipage/canvas.html#canvasdrawimage
interface mixin CanvasDrawImage {
undefined drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy);
undefined drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy, unrestricted double dw, unrestricted double dh);
undefined 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);
};

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <LibWeb/HTML/Path2D.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvasdrawpath
class CanvasDrawPath {
public:
virtual ~CanvasDrawPath() = default;
virtual void begin_path() = 0;
virtual void fill(StringView fill_rule) = 0;
virtual void fill(Path2D& path, StringView fill_rule) = 0;
virtual void stroke() = 0;
virtual void stroke(Path2D const& path) = 0;
virtual void clip(StringView fill_rule) = 0;
virtual void clip(Path2D& path, StringView fill_rule) = 0;
virtual bool is_point_in_path(double x, double y, StringView fill_rule) = 0;
virtual bool is_point_in_path(Path2D const& path, double x, double y, StringView fill_rule) = 0;
protected:
CanvasDrawPath() = default;
};
}

View file

@ -0,0 +1,27 @@
#import <HTML/Path2D.idl>
// https://html.spec.whatwg.org/multipage/canvas.html#canvasfillrule
enum CanvasFillRule { "nonzero", "evenodd" };
// https://html.spec.whatwg.org/multipage/canvas.html#canvasdrawpath
interface mixin CanvasDrawPath {
undefined beginPath();
// FIXME: `DOMString` should be `CanvasFillRule`
undefined fill(optional DOMString fillRule = "nonzero");
// FIXME: `DOMString` should be `CanvasFillRule`
undefined fill(Path2D path, optional DOMString fillRule = "nonzero");
undefined stroke();
undefined stroke(Path2D path);
// FIXME: `DOMString` should be `CanvasFillRule`
undefined clip(optional DOMString fillRule = "nonzero");
// FIXME: `DOMString` should be `CanvasFillRule`
undefined clip(Path2D path, optional DOMString fillRule = "nonzero");
// FIXME: `DOMString` should be `CanvasFillRule`
boolean isPointInPath(unrestricted double x, unrestricted double y, optional DOMString fillRule = "nonzero");
// FIXME: `DOMString` should be `CanvasFillRule`
boolean isPointInPath(Path2D path, unrestricted double x, unrestricted double y, optional DOMString fillRule = "nonzero");
[FIXME] boolean isPointInStroke(unrestricted double x, unrestricted double y);
[FIXME] boolean isPointInStroke(Path2D path, unrestricted double x, unrestricted double y);
};

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/String.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/HTML/Canvas/CanvasState.h>
#include <LibWeb/HTML/CanvasGradient.h>
#include <LibWeb/HTML/CanvasPattern.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvasfillstrokestyles
template<typename IncludingClass>
class CanvasFillStrokeStyles {
public:
~CanvasFillStrokeStyles() = default;
using FillOrStrokeStyleVariant = Variant<String, JS::Handle<CanvasGradient>, JS::Handle<CanvasPattern>>;
void set_fill_style(FillOrStrokeStyleVariant style)
{
auto& realm = static_cast<IncludingClass&>(*this).realm();
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-fillstyle
style.visit(
// 1. If the given value is a string, then:
[&](String const& string) {
// 1. Let context be this's canvas attribute's value, if that is an element; otherwise null.
auto parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext(realm), string);
// 2. Let parsedValue be the result of parsing the given value with context if non-null.
// FIXME: Parse a color value
// https://drafts.csswg.org/css-color/#parse-a-css-color-value
auto style_value = parser.parse_as_css_value(CSS::PropertyID::Color);
if (style_value && style_value->has_color()) {
auto parsedValue = style_value->to_color(OptionalNone());
// 4. Set this's fill style to parsedValue.
my_drawing_state().fill_style = parsedValue;
} else {
// 3. If parsedValue is failure, then return.
return;
}
// 5. Return.
return;
},
[&](auto fill_or_stroke_style) {
// FIXME: 2. If the given value is a CanvasPattern object that is marked as not origin-clean, then set this's origin-clean flag to false.
// 3. Set this's fill style to the given value.
my_drawing_state().fill_style = fill_or_stroke_style;
});
}
FillOrStrokeStyleVariant fill_style() const
{
return my_drawing_state().fill_style.to_js_fill_or_stroke_style();
}
void set_stroke_style(FillOrStrokeStyleVariant style)
{
auto& realm = static_cast<IncludingClass&>(*this).realm();
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-strokestyle
style.visit(
// 1. If the given value is a string, then:
[&](String const& string) {
// 1. Let context be this's canvas attribute's value, if that is an element; otherwise null.
auto parser = CSS::Parser::Parser::create(CSS::Parser::ParsingContext(realm), string);
// 2. Let parsedValue be the result of parsing the given value with context if non-null.
// FIXME: Parse a color value
// https://drafts.csswg.org/css-color/#parse-a-css-color-value
auto style_value = parser.parse_as_css_value(CSS::PropertyID::Color);
if (style_value && style_value->has_color()) {
auto parsedValue = style_value->to_color(OptionalNone());
// 4. Set this's stroke style to parsedValue.
my_drawing_state().stroke_style = parsedValue;
} else {
// 3. If parsedValue is failure, then return.
return;
}
// 5. Return.
return;
},
[&](auto fill_or_stroke_style) {
// FIXME: 2. If the given value is a CanvasPattern object that is marked as not origin-clean, then set this's origin-clean flag to false.
// 3. Set this's stroke style to the given value.
my_drawing_state().fill_style = fill_or_stroke_style;
});
}
FillOrStrokeStyleVariant stroke_style() const
{
return my_drawing_state().stroke_style.to_js_fill_or_stroke_style();
}
WebIDL::ExceptionOr<JS::NonnullGCPtr<CanvasGradient>> create_radial_gradient(double x0, double y0, double r0, double x1, double y1, double r1)
{
auto& realm = static_cast<IncludingClass&>(*this).realm();
return CanvasGradient::create_radial(realm, x0, y0, r0, x1, y1, r1);
}
JS::NonnullGCPtr<CanvasGradient> create_linear_gradient(double x0, double y0, double x1, double y1)
{
auto& realm = static_cast<IncludingClass&>(*this).realm();
return CanvasGradient::create_linear(realm, x0, y0, x1, y1).release_value_but_fixme_should_propagate_errors();
}
JS::NonnullGCPtr<CanvasGradient> create_conic_gradient(double start_angle, double x, double y)
{
auto& realm = static_cast<IncludingClass&>(*this).realm();
return CanvasGradient::create_conic(realm, start_angle, x, y).release_value_but_fixme_should_propagate_errors();
}
WebIDL::ExceptionOr<JS::GCPtr<CanvasPattern>> create_pattern(CanvasImageSource const& image, StringView repetition)
{
auto& realm = static_cast<IncludingClass&>(*this).realm();
return CanvasPattern::create(realm, image, repetition);
}
protected:
CanvasFillStrokeStyles() = default;
private:
CanvasState::DrawingState& my_drawing_state() { return reinterpret_cast<IncludingClass&>(*this).drawing_state(); }
CanvasState::DrawingState const& my_drawing_state() const { return reinterpret_cast<IncludingClass const&>(*this).drawing_state(); }
};
}

View file

@ -0,0 +1,14 @@
#import <HTML/CanvasGradient.idl>
#import <HTML/CanvasPattern.idl>
#import <HTML/HTMLCanvasElement.idl>
#import <HTML/HTMLImageElement.idl>
// https://html.spec.whatwg.org/multipage/canvas.html#canvasfillstrokestyles
interface mixin CanvasFillStrokeStyles {
attribute (DOMString or CanvasGradient or CanvasPattern) strokeStyle;
attribute (DOMString or CanvasGradient or CanvasPattern) fillStyle;
CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1);
CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1);
CanvasGradient createConicGradient(double startAngle, double x, double y);
CanvasPattern? createPattern(CanvasImageSource image, [LegacyNullToEmptyString] DOMString repetition);
};

View file

@ -0,0 +1,5 @@
// https://html.spec.whatwg.org/multipage/canvas.html#canvasfilters
interface mixin CanvasFilters {
// filters
[FIXME] attribute DOMString filter; // (default "none")
};

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/HTML/ImageData.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvasimagedata
class CanvasImageData {
public:
virtual ~CanvasImageData() = default;
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> create_image_data(int width, int height, Optional<ImageDataSettings> const& settings = {}) const = 0;
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> create_image_data(ImageData const&) const = 0;
virtual WebIDL::ExceptionOr<JS::GCPtr<ImageData>> get_image_data(int x, int y, int width, int height, Optional<ImageDataSettings> const& settings = {}) const = 0;
virtual void put_image_data(ImageData const&, float x, float y) = 0;
protected:
CanvasImageData() = default;
};
}

View file

@ -0,0 +1,12 @@
#import <HTML/ImageData.idl>
// https://html.spec.whatwg.org/multipage/canvas.html#canvasimagedata
interface mixin CanvasImageData {
ImageData createImageData([EnforceRange] long sw, [EnforceRange] long sh, optional ImageDataSettings settings = {});
ImageData createImageData(ImageData imagedata);
ImageData getImageData([EnforceRange] long sx, [EnforceRange] long sy, [EnforceRange] long sw, [EnforceRange] long sh, optional ImageDataSettings settings = {});
undefined putImageData(ImageData imagedata, [EnforceRange] long dx, [EnforceRange] long dy);
[FIXME] undefined putImageData(ImageData imagedata, [EnforceRange] long dx, [EnforceRange] long dy, [EnforceRange] long dirtyX, [EnforceRange] long dirtyY, [EnforceRange] long dirtyWidth, [EnforceRange] long dirtyHeight);
};

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2023, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/HTML/ImageData.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvasimagesmoothing
class CanvasImageSmoothing {
public:
virtual ~CanvasImageSmoothing() = default;
virtual bool image_smoothing_enabled() const = 0;
virtual void set_image_smoothing_enabled(bool) = 0;
virtual Bindings::ImageSmoothingQuality image_smoothing_quality() const = 0;
virtual void set_image_smoothing_quality(Bindings::ImageSmoothingQuality) = 0;
protected:
CanvasImageSmoothing() = default;
};
}

View file

@ -0,0 +1,8 @@
#import <HTML/CanvasRenderingContext2D.idl>
// https://html.spec.whatwg.org/multipage/canvas.html#canvasimagesmoothing
interface mixin CanvasImageSmoothing {
// image smoothing
attribute boolean imageSmoothingEnabled; // (default true)
attribute ImageSmoothingQuality imageSmoothingQuality; // (default low)
};

View file

@ -0,0 +1,441 @@
/*
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Vector2.h>
#include <LibWeb/HTML/Canvas/CanvasPath.h>
namespace Web::HTML {
Gfx::AffineTransform CanvasPath::active_transform() const
{
if (m_canvas_state.has_value())
return m_canvas_state->drawing_state().transform;
return {};
}
void CanvasPath::ensure_subpath(float x, float y)
{
if (m_path.is_empty())
m_path.move_to(Gfx::FloatPoint { x, y });
}
void CanvasPath::close_path()
{
m_path.close();
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-moveto
void CanvasPath::move_to(float x, float y)
{
// 1. If either of the arguments are infinite or NaN, then return.
if (!isfinite(x) || !isfinite(y))
return;
// 2. Create a new subpath with the specified point as its first (and only) point.
m_path.move_to(Gfx::FloatPoint { x, y });
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-lineto
void CanvasPath::line_to(float x, float y)
{
// 1. If either of the arguments are infinite or NaN, then return.
if (!isfinite(x) || !isfinite(y))
return;
if (m_path.is_empty()) {
// 2. If the object's path has no subpaths, then ensure there is a subpath for (x, y).
ensure_subpath(x, y);
} else {
// 3. Otherwise, connect the last point in the subpath to the given point (x, y) using a straight line,
// and then add the given point (x, y) to the subpath.
m_path.line_to(Gfx::FloatPoint { x, y });
}
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-quadraticcurveto
void CanvasPath::quadratic_curve_to(float cpx, float cpy, float x, float y)
{
// 1. If any of the arguments are infinite or NaN, then return.
if (!isfinite(cpx) || !isfinite(cpy) || !isfinite(x) || !isfinite(y))
return;
// 2. Ensure there is a subpath for (cpx, cpy)
ensure_subpath(cpx, cpy);
// 3. Connect the last point in the subpath to the given point (x, y) using a quadratic Bézier curve with control point (cpx, cpy).
// 4. Add the given point (x, y) to the subpath.
m_path.quadratic_bezier_curve_to(Gfx::FloatPoint { cpx, cpy }, Gfx::FloatPoint { x, y });
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-beziercurveto
void CanvasPath::bezier_curve_to(double cp1x, double cp1y, double cp2x, double cp2y, double x, double y)
{
// 1. If any of the arguments are infinite or NaN, then return.
if (!isfinite(cp1x) || !isfinite(cp1y) || !isfinite(cp2x) || !isfinite(cp2y) || !isfinite(x) || !isfinite(y))
return;
// 2. Ensure there is a subpath for (cp1x, cp1y)
ensure_subpath(cp1x, cp1y);
// 3. Connect the last point in the subpath to the given point (x, y) using a cubic Bézier curve with control poits (cp1x, cp1y) and (cp2x, cp2y).
// 4. Add the point (x, y) to the subpath.
m_path.cubic_bezier_curve_to(
Gfx::FloatPoint { cp1x, cp1y }, Gfx::FloatPoint { cp2x, cp2y }, Gfx::FloatPoint { x, y });
}
WebIDL::ExceptionOr<void> CanvasPath::arc(float x, float y, float radius, float start_angle, float end_angle, bool counter_clockwise)
{
if (radius < 0)
return WebIDL::IndexSizeError::create(m_self->realm(), MUST(String::formatted("The radius provided ({}) is negative.", radius)));
return ellipse(x, y, radius, radius, 0, start_angle, end_angle, counter_clockwise);
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-ellipse
WebIDL::ExceptionOr<void> CanvasPath::ellipse(float x, float y, float radius_x, float radius_y, float rotation, float start_angle, float end_angle, bool counter_clockwise)
{
// 1. If any of the arguments are infinite or NaN, then return.
if (!isfinite(x) || !isfinite(y) || !isfinite(radius_x) || !isfinite(radius_y) || !isfinite(rotation) || !isfinite(start_angle) || !isfinite(end_angle))
return {};
// 2. If either radiusX or radiusY are negative, then throw an "IndexSizeError" DOMException.
if (radius_x < 0)
return WebIDL::IndexSizeError::create(m_self->realm(), MUST(String::formatted("The major-axis radius provided ({}) is negative.", radius_x)));
if (radius_y < 0)
return WebIDL::IndexSizeError::create(m_self->realm(), MUST(String::formatted("The minor-axis radius provided ({}) is negative.", radius_y)));
// "If counterclockwise is false and endAngle startAngle is greater than or equal to 2π,
// or, if counterclockwise is true and startAngle endAngle is greater than or equal to 2π,
// then the arc is the whole circumference of this ellipse"
// Also draw the full ellipse if making a non-zero whole number of turns.
if (constexpr float tau = M_PI * 2; (!counter_clockwise && (end_angle - start_angle) >= tau)
|| (counter_clockwise && (start_angle - end_angle) >= tau)
|| (start_angle != end_angle && fmodf(start_angle - end_angle, tau) == 0)) {
start_angle = 0;
// FIXME: elliptical_arc_to() incorrectly handles the case where the start/end points are very close.
// So we slightly fudge the numbers here to correct for that.
end_angle = tau * 0.9999f;
counter_clockwise = false;
} else {
start_angle = fmodf(start_angle, tau);
end_angle = fmodf(end_angle, tau);
}
// Then, figure out where the ends of the arc are.
// To do so, we can pretend that the center of this ellipse is at (0, 0),
// and the whole coordinate system is rotated `rotation` radians around the x axis, centered on `center`.
// The sign of the resulting relative positions is just whether our angle is on one of the left quadrants.
float sin_rotation;
float cos_rotation;
AK::sincos(rotation, sin_rotation, cos_rotation);
auto resolve_point_with_angle = [&](float angle) {
auto tan_relative = tanf(angle);
auto tan2 = tan_relative * tan_relative;
auto ab = radius_x * radius_y;
auto a2 = radius_x * radius_x;
auto b2 = radius_y * radius_y;
auto sqrt = sqrtf(b2 + a2 * tan2);
auto relative_x_position = ab / sqrt;
auto relative_y_position = ab * tan_relative / sqrt;
// Make sure to set the correct sign
// -1 if 0 ≤ θ < 90° or 270°< θ ≤ 360°
// 1 if 90° < θ< 270°
float sn = cosf(angle) >= 0 ? 1 : -1;
relative_x_position *= sn;
relative_y_position *= sn;
// Now rotate it (back) around the center point by 'rotation' radians, then move it back to our actual origin.
auto relative_rotated_x_position = relative_x_position * cos_rotation - relative_y_position * sin_rotation;
auto relative_rotated_y_position = relative_x_position * sin_rotation + relative_y_position * cos_rotation;
return Gfx::FloatPoint { relative_rotated_x_position + x, relative_rotated_y_position + y };
};
auto start_point = resolve_point_with_angle(start_angle);
auto end_point = resolve_point_with_angle(end_angle);
float delta_theta;
if (counter_clockwise) {
delta_theta = start_angle - end_angle;
} else {
delta_theta = end_angle - start_angle;
}
if (delta_theta < 0)
delta_theta += AK::Pi<float> * 2;
// 3. If canvasPath's path has any subpaths, then add a straight line from the last point in the subpath to the start point of the arc.
if (!m_path.is_empty())
m_path.line_to(start_point);
else
m_path.move_to(start_point);
// 4. Add the start and end points of the arc to the subpath, and connect them with an arc.
m_path.elliptical_arc_to(
Gfx::FloatPoint { end_point },
Gfx::FloatSize { radius_x, radius_y },
rotation,
delta_theta > AK::Pi<float>, !counter_clockwise);
return {};
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-arcto
WebIDL::ExceptionOr<void> CanvasPath::arc_to(double x1, double y1, double x2, double y2, double radius)
{
// 1. If any of the arguments are infinite or NaN, then return.
if (!isfinite(x1) || !isfinite(y1) || !isfinite(x2) || !isfinite(y2) || !isfinite(radius))
return {};
// 2. Ensure there is a subpath for (x1, y1).
ensure_subpath(x1, y1);
// 3. If radius is negative, then throw an "IndexSizeError" DOMException.
if (radius < 0)
return WebIDL::IndexSizeError::create(m_self->realm(), MUST(String::formatted("The radius provided ({}) is negative.", radius)));
auto transform = active_transform();
// 4. Let the point (x0, y0) be the last point in the subpath,
// transformed by the inverse of the current transformation matrix
// (so that it is in the same coordinate system as the points passed to the method).
// Point (x0, y0)
auto p0 = transform.inverse().value_or(Gfx::AffineTransform()).map(m_path.last_point());
// Point (x1, y1)
auto p1 = Gfx::FloatPoint { x1, y1 };
// Point (x2, y2)
auto p2 = Gfx::FloatPoint { x2, y2 };
// 5. If the point (x0, y0) is equal to the point (x1, y1),
// or if the point (x1, y1) is equal to the point (x2, y2),
// or if radius is zero, then add the point (x1, y1) to the subpath,
// and connect that point to the previous point (x0, y0) by a straight line.
if (p0 == p1 || p1 == p2 || radius == 0) {
m_path.line_to(p1);
return {};
}
auto v1 = Gfx::FloatVector2 { p0.x() - p1.x(), p0.y() - p1.y() };
auto v2 = Gfx::FloatVector2 { p2.x() - p1.x(), p2.y() - p1.y() };
auto cos_theta = v1.dot(v2) / (v1.length() * v2.length());
// 6. Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2) all lie on a single straight line,
// then add the point (x1, y1) to the subpath,
// and connect that point to the previous point (x0, y0) by a straight line.
if (-1 == cos_theta || 1 == cos_theta) {
m_path.line_to(p1);
return {};
}
// 7. Otherwise, let The Arc be the shortest arc given by circumference of the circle that has radius radius,
// and that has one point tangent to the half-infinite line that crosses the point (x0, y0) and ends at the point (x1, y1),
// and that has a different point tangent to the half-infinite line that ends at the point (x1, y1) and crosses the point (x2, y2).
// The points at which this circle touches these two lines are called the start and end tangent points respectively.
auto adjacent = radius / static_cast<double>(tan(acos(cos_theta) / 2));
auto factor1 = adjacent / static_cast<double>(v1.length());
auto x3 = static_cast<double>(p1.x()) + factor1 * static_cast<double>(p0.x() - p1.x());
auto y3 = static_cast<double>(p1.y()) + factor1 * static_cast<double>(p0.y() - p1.y());
auto start_tangent = Gfx::FloatPoint { x3, y3 };
auto factor2 = adjacent / static_cast<double>(v2.length());
auto x4 = static_cast<double>(p1.x()) + factor2 * static_cast<double>(p2.x() - p1.x());
auto y4 = static_cast<double>(p1.y()) + factor2 * static_cast<double>(p2.y() - p1.y());
auto end_tangent = Gfx::FloatPoint { x4, y4 };
// Connect the point (x0, y0) to the start tangent point by a straight line, adding the start tangent point to the subpath.
m_path.line_to(start_tangent);
bool const large_arc = false; // always small since tangent points define arc endpoints and lines meet at (x1, y1)
auto cross_product = v1.x() * v2.y() - v1.y() * v2.x();
bool const sweep = cross_product < 0; // right-hand rule, true means clockwise
// and then connect the start tangent point to the end tangent point by The Arc, adding the end tangent point to the subpath.
m_path.arc_to(end_tangent, radius, large_arc, sweep);
return {};
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-rect
void CanvasPath::rect(double x, double y, double w, double h)
{
// 1. If any of the arguments are infinite or NaN, then return.
if (!isfinite(x) || !isfinite(y) || !isfinite(w) || !isfinite(h))
return;
// 2. Create a new subpath containing just the four points (x, y), (x+w, y), (x+w, y+h), (x, y+h), in that order, with those four points connected by straight lines.
m_path.move_to(Gfx::FloatPoint { x, y });
m_path.line_to(Gfx::FloatPoint { x + w, y });
m_path.line_to(Gfx::FloatPoint { x + w, y + h });
m_path.line_to(Gfx::FloatPoint { x, y + h });
// 3. Mark the subpath as closed.
m_path.close();
// 4. Create a new subpath with the point (x, y) as the only point in the subpath.
m_path.move_to(Gfx::FloatPoint { x, y });
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect
WebIDL::ExceptionOr<void> CanvasPath::round_rect(double x, double y, double w, double h, Variant<double, Geometry::DOMPointInit, Vector<Variant<double, Geometry::DOMPointInit>>> radii)
{
using Radius = Variant<double, Geometry::DOMPointInit>;
// 1. If any of x, y, w, or h are infinite or NaN, then return.
if (!isfinite(x) || !isfinite(y) || !isfinite(w) || !isfinite(h))
return {};
// 2. If radii is an unrestricted double or DOMPointInit, then set radii to « radii ».
if (radii.has<double>() || radii.has<Geometry::DOMPointInit>()) {
Vector<Radius> radii_list;
if (radii.has<double>())
radii_list.append(radii.get<double>());
else
radii_list.append(radii.get<Geometry::DOMPointInit>());
radii = radii_list;
}
// 3. If radii is not a list of size one, two, three, or four, then throw a RangeError.
if (radii.get<Vector<Radius>>().is_empty() || radii.get<Vector<Radius>>().size() > 4)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "roundRect: Can have between 1 and 4 radii"sv };
// 4. Let normalizedRadii be an empty list.
Vector<Geometry::DOMPointInit> normalized_radii;
// 5. For each radius of radii:
for (auto const& radius : radii.get<Vector<Radius>>()) {
// 5.1. If radius is a DOMPointInit:
if (radius.has<Geometry::DOMPointInit>()) {
auto const& radius_as_dom_point = radius.get<Geometry::DOMPointInit>();
// 5.1.1. If radius["x"] or radius["y"] is infinite or NaN, then return.
if (!isfinite(radius_as_dom_point.x) || !isfinite(radius_as_dom_point.y))
return {};
// 5.1.2. If radius["x"] or radius["y"] is negative, then throw a RangeError.
if (radius_as_dom_point.x < 0 || radius_as_dom_point.y < 0)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "roundRect: Radius can't be negative"sv };
// 5.1.3. Otherwise, append radius to normalizedRadii.
normalized_radii.append(radius_as_dom_point);
}
// 5.2. If radius is a unrestricted double:
if (radius.has<double>()) {
auto radius_as_double = radius.get<double>();
// 5.2.1. If radius is infinite or NaN, then return.
if (!isfinite(radius_as_double))
return {};
// 5.2.2. If radius is negative, then throw a RangeError.
if (radius_as_double < 0)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "roundRect: Radius can't be negative"sv };
// 5.2.3. Otherwise append «[ "x" → radius, "y" → radius ]» to normalizedRadii.
normalized_radii.append(Geometry::DOMPointInit { radius_as_double, radius_as_double });
}
}
// 6. Let upperLeft, upperRight, lowerRight, and lowerLeft be null.
Geometry::DOMPointInit upper_left {};
Geometry::DOMPointInit upper_right {};
Geometry::DOMPointInit lower_right {};
Geometry::DOMPointInit lower_left {};
// 7. If normalizedRadii's size is 4, then set upperLeft to normalizedRadii[0], set upperRight to normalizedRadii[1], set lowerRight to normalizedRadii[2], and set lowerLeft to normalizedRadii[3].
if (normalized_radii.size() == 4) {
upper_left = normalized_radii.at(0);
upper_right = normalized_radii.at(1);
lower_right = normalized_radii.at(2);
lower_left = normalized_radii.at(3);
}
// 8. If normalizedRadii's size is 3, then set upperLeft to normalizedRadii[0], set upperRight and lowerLeft to normalizedRadii[1], and set lowerRight to normalizedRadii[2].
if (normalized_radii.size() == 3) {
upper_left = normalized_radii.at(0);
upper_right = lower_left = normalized_radii.at(1);
lower_right = normalized_radii.at(2);
}
// 9. If normalizedRadii's size is 2, then set upperLeft and lowerRight to normalizedRadii[0] and set upperRight and lowerLeft to normalizedRadii[1].
if (normalized_radii.size() == 2) {
upper_left = lower_right = normalized_radii.at(0);
upper_right = lower_left = normalized_radii.at(1);
}
// 10. If normalizedRadii's size is 1, then set upperLeft, upperRight, lowerRight, and lowerLeft to normalizedRadii[0].
if (normalized_radii.size() == 1)
upper_left = upper_right = lower_right = lower_left = normalized_radii.at(0);
// 11. Corner curves must not overlap. Scale all radii to prevent this:
// 11.1. Let top be upperLeft["x"] + upperRight["x"].
double top = upper_left.x + upper_right.x;
// 11.2. Let right be upperRight["y"] + lowerRight["y"].
double right = upper_right.y + lower_right.y;
// 11.3. Let bottom be lowerRight["x"] + lowerLeft["x"].
double bottom = lower_right.x + lower_left.x;
// 11.4. Let left be upperLeft["y"] + lowerLeft["y"].
double left = upper_left.y + lower_left.y;
// 11.5. Let scale be the minimum value of the ratios w / top, h / right, w / bottom, h / left.
double scale = AK::min(AK::min(w / top, h / right), AK::min(w / bottom, h / left));
// 11.6. If scale is less than 1, then set the x and y members of upperLeft, upperRight, lowerLeft, and lowerRight to their current values multiplied by scale.
if (scale < 1) {
upper_left.x *= scale;
upper_left.y *= scale;
upper_right.x *= scale;
upper_right.y *= scale;
lower_left.x *= scale;
lower_left.y *= scale;
lower_right.x *= scale;
lower_right.y *= scale;
}
// 12. Create a new subpath:
bool large_arc = false;
bool sweep = true;
// 12.1. Move to the point (x + upperLeft["x"], y).
m_path.move_to(Gfx::FloatPoint { x + upper_left.x, y });
// 12.2. Draw a straight line to the point (x + w upperRight["x"], y).
m_path.line_to(Gfx::FloatPoint { x + w - upper_right.x, y });
// 12.3. Draw an arc to the point (x + w, y + upperRight["y"]).
m_path.elliptical_arc_to(Gfx::FloatPoint { x + w, y + upper_right.y }, { upper_right.x, upper_right.y }, 0, large_arc, sweep);
// 12.4. Draw a straight line to the point (x + w, y + h lowerRight["y"]).
m_path.line_to(Gfx::FloatPoint { x + w, y + h - lower_right.y });
// 12.5. Draw an arc to the point (x + w lowerRight["x"], y + h).
m_path.elliptical_arc_to(Gfx::FloatPoint { x + w - lower_right.x, y + h }, { lower_right.x, lower_right.y }, 0, large_arc, sweep);
// 12.6. Draw a straight line to the point (x + lowerLeft["x"], y + h).
m_path.line_to(Gfx::FloatPoint { x + lower_left.x, y + h });
// 12.7. Draw an arc to the point (x, y + h lowerLeft["y"]).
m_path.elliptical_arc_to(Gfx::FloatPoint { x, y + h - lower_left.y }, { lower_left.x, lower_left.y }, 0, large_arc, sweep);
// 12.8. Draw a straight line to the point (x, y + upperLeft["y"]).
m_path.line_to(Gfx::FloatPoint { x, y + upper_left.y });
// 12.9. Draw an arc to the point (x + upperLeft["x"], y).
m_path.elliptical_arc_to(Gfx::FloatPoint { x + upper_left.x, y }, { upper_left.x, upper_left.y }, 0, large_arc, sweep);
// 13. Mark the subpath as closed.
m_path.close();
// 14. Create a new subpath with the point (x, y) as the only point in the subpath.
m_path.move_to(Gfx::FloatPoint { x, y });
return {};
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGfx/Path.h>
#include <LibWeb/Geometry/DOMPointReadOnly.h>
#include <LibWeb/HTML/Canvas/CanvasState.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvaspath
class CanvasPath {
public:
~CanvasPath() = default;
void close_path();
void move_to(float x, float y);
void line_to(float x, float y);
void quadratic_curve_to(float cx, float cy, float x, float y);
void bezier_curve_to(double cp1x, double cp1y, double cp2x, double cp2y, double x, double y);
WebIDL::ExceptionOr<void> arc_to(double x1, double y1, double x2, double y2, double radius);
void rect(double x, double y, double w, double h);
WebIDL::ExceptionOr<void> round_rect(double x, double y, double w, double h, Variant<double, Geometry::DOMPointInit, Vector<Variant<double, Geometry::DOMPointInit>>> radii = { 0 });
WebIDL::ExceptionOr<void> arc(float x, float y, float radius, float start_angle, float end_angle, bool counter_clockwise);
WebIDL::ExceptionOr<void> ellipse(float x, float y, float radius_x, float radius_y, float rotation, float start_angle, float end_angle, bool counter_clockwise);
Gfx::Path& path() { return m_path; }
Gfx::Path const& path() const { return m_path; }
protected:
explicit CanvasPath(Bindings::PlatformObject& self)
: m_self(self)
{
}
explicit CanvasPath(Bindings::PlatformObject& self, CanvasState const& canvas_state)
: m_self(self)
, m_canvas_state(canvas_state)
{
}
private:
Gfx::AffineTransform active_transform() const;
void ensure_subpath(float x, float y);
JS::NonnullGCPtr<Bindings::PlatformObject> m_self;
Optional<CanvasState const&> m_canvas_state;
Gfx::Path m_path;
};
}

View file

@ -0,0 +1,15 @@
#import <Geometry/DOMPointReadOnly.idl>
// https://html.spec.whatwg.org/multipage/canvas.html#canvaspath
interface mixin CanvasPath {
undefined closePath();
undefined moveTo(unrestricted double x, unrestricted double y);
undefined lineTo(unrestricted double x, unrestricted double y);
undefined quadraticCurveTo(unrestricted double cpx, unrestricted double cpy, unrestricted double x, unrestricted double y);
undefined bezierCurveTo(unrestricted double cp1x, unrestricted double cp1y, unrestricted double cp2x, unrestricted double cp2y, unrestricted double x, unrestricted double y);
undefined arcTo(unrestricted double x1, unrestricted double y1, unrestricted double x2, unrestricted double y2, unrestricted double radius);
undefined rect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
undefined roundRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>) radii = 0);
undefined arc(unrestricted double x, unrestricted double y, unrestricted double radius, unrestricted double startAngle, unrestricted double endAngle, optional boolean counterclockwise = false);
undefined ellipse(unrestricted double x, unrestricted double y, unrestricted double radiusX, unrestricted double radiusY, unrestricted double rotation, unrestricted double startAngle, unrestricted double endAngle, optional boolean counterclockwise = false);
};

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/HTML/Canvas/CanvasState.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvaspathdrawingstyles
template<typename IncludingClass>
class CanvasPathDrawingStyles {
public:
~CanvasPathDrawingStyles() = default;
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-linewidth
void set_line_width(float line_width)
{
// On setting, zero, negative, infinite, and NaN values must be ignored, leaving the value unchanged;
if (line_width <= 0 || !isfinite(line_width))
return;
// other values must change the current value to the new value.
my_drawing_state().line_width = line_width;
}
float line_width() const
{
// On getting, it must return the current value.
return my_drawing_state().line_width;
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-linecap
void set_line_cap(Bindings::CanvasLineCap line_cap)
{
// On setting, the current value must be changed to the new value.
my_drawing_state().line_cap = line_cap;
}
Bindings::CanvasLineCap line_cap() const
{
// On getting, it must return the current value.
return my_drawing_state().line_cap;
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-linejoin
void set_line_join(Bindings::CanvasLineJoin line_join)
{
// On setting, the current value must be changed to the new value.
my_drawing_state().line_join = line_join;
}
Bindings::CanvasLineJoin line_join() const
{
// On getting, it must return the current value.
return my_drawing_state().line_join;
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-miterlimit
void set_miter_limit(float miter_limit)
{
// On setting, zero, negative, infinite, and NaN values must be ignored, leaving the value unchanged;
if (miter_limit <= 0 || !isfinite(miter_limit))
return;
// other values must change the current value to the new value.
my_drawing_state().miter_limit = miter_limit;
}
float miter_limit() const
{
// On getting, it must return the current value.
return my_drawing_state().miter_limit;
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-setlinedash
void set_line_dash(Vector<double> segments)
{
// The setLineDash(segments) method, when invoked, must run these steps:
// 1. If any value in segments is not finite (e.g. an Infinity or a NaN value), or if any value is negative (less than zero), then return
// (without throwing an exception; user agents could show a message on a developer console, though, as that would be helpful for debugging).
for (auto const& segment : segments) {
if (!isfinite(segment) || segment < 0)
return;
}
// 2. If the number of elements in segments is odd, then let segments be the concatenation of two copies of segments.
if (segments.size() % 2 == 1)
segments.extend(segments);
// 3. Let the object's dash list be segments.
my_drawing_state().dash_list = segments;
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-getlinedash
Vector<double> get_line_dash()
{
// When the getLineDash() method is invoked, it must return a sequence whose values are the values of the object's dash list, in the same order.
return my_drawing_state().dash_list;
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-linedashoffset
void set_line_dash_offset(float line_dash_offset)
{
// On setting, infinite and NaN values must be ignored, leaving the value unchanged;
if (!isfinite(line_dash_offset))
return;
// other values must change the current value to the new value.
my_drawing_state().line_dash_offset = line_dash_offset;
}
float line_dash_offset() const
{
// On getting, it must return the current value.
return my_drawing_state().line_dash_offset;
}
protected:
CanvasPathDrawingStyles() = default;
private:
CanvasState::DrawingState& my_drawing_state() { return reinterpret_cast<IncludingClass&>(*this).drawing_state(); }
CanvasState::DrawingState const& my_drawing_state() const { return reinterpret_cast<IncludingClass const&>(*this).drawing_state(); }
};
}

View file

@ -0,0 +1,15 @@
// https://html.spec.whatwg.org/multipage/canvas.html#canvaslinecap
// enum CanvasLineCap { "butt", "round", "square" };
// enum CanvasLineJoin { "round", "bevel", "miter" };
// https://html.spec.whatwg.org/multipage/canvas.html#canvaspathdrawingstyles
interface mixin CanvasPathDrawingStyles {
attribute unrestricted double lineWidth;
attribute CanvasLineCap lineCap;
attribute CanvasLineJoin lineJoin;
attribute unrestricted double miterLimit;
undefined setLineDash(sequence<unrestricted double> segments);
sequence<unrestricted double> getLineDash();
attribute unrestricted double lineDashOffset;
};

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvasrect
class CanvasRect {
public:
virtual ~CanvasRect() = default;
virtual void fill_rect(float x, float y, float width, float height) = 0;
virtual void stroke_rect(float x, float y, float width, float height) = 0;
virtual void clear_rect(float x, float y, float width, float height) = 0;
protected:
CanvasRect() = default;
};
}

View file

@ -0,0 +1,6 @@
// https://html.spec.whatwg.org/multipage/canvas.html#canvasrect
interface mixin CanvasRect {
undefined clearRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
undefined fillRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
undefined strokeRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
};

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2024, İbrahim UYSAL <uysalibov@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/String.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/HTML/Canvas/CanvasState.h>
#include <LibWeb/HTML/CanvasGradient.h>
#include <LibWeb/HTML/CanvasPattern.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvasshadowstyles
template<typename IncludingClass>
class CanvasShadowStyles {
public:
~CanvasShadowStyles() = default;
virtual float shadow_offset_x() const = 0;
virtual void set_shadow_offset_x(float offsetX) = 0;
virtual float shadow_offset_y() const = 0;
virtual void set_shadow_offset_y(float offsetY) = 0;
virtual String shadow_color() const = 0;
virtual void set_shadow_color(String color) = 0;
protected:
CanvasShadowStyles() = default;
private:
CanvasState::DrawingState& my_drawing_state() { return reinterpret_cast<IncludingClass&>(*this).drawing_state(); }
CanvasState::DrawingState const& my_drawing_state() const { return reinterpret_cast<IncludingClass const&>(*this).drawing_state(); }
};
}

View file

@ -0,0 +1,8 @@
// https://html.spec.whatwg.org/multipage/canvas.html#canvasshadowstyles
interface mixin CanvasShadowStyles {
// shadows
attribute unrestricted double shadowOffsetX; // (default 0)
attribute unrestricted double shadowOffsetY; // (default 0)
[FIXME] attribute unrestricted double shadowBlur; // (default 0)
attribute DOMString shadowColor; // (default transparent black)
};

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Painter.h>
#include <LibWeb/HTML/Canvas/CanvasState.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-save
void CanvasState::save()
{
// The save() method steps are to push a copy of the current drawing state onto the drawing state stack.
m_drawing_state_stack.append(m_drawing_state);
if (auto* painter = painter_for_canvas_state())
painter->save();
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-restore
void CanvasState::restore()
{
// The restore() method steps are to pop the top entry in the drawing state stack, and reset the drawing state it describes. If there is no saved state, then the method must do nothing.
if (m_drawing_state_stack.is_empty())
return;
m_drawing_state = m_drawing_state_stack.take_last();
if (auto* painter = painter_for_canvas_state())
painter->restore();
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-reset
void CanvasState::reset()
{
// The reset() method steps are to reset the rendering context to its default state.
reset_to_default_state();
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-iscontextlost
bool CanvasState::is_context_lost()
{
// The isContextLost() method steps are to return this's context lost.
return m_context_lost;
}
NonnullRefPtr<Gfx::PaintStyle> CanvasState::FillOrStrokeStyle::to_gfx_paint_style()
{
return m_fill_or_stroke_style.visit(
[&](Gfx::Color color) -> NonnullRefPtr<Gfx::PaintStyle> {
if (!m_color_paint_style)
m_color_paint_style = Gfx::SolidColorPaintStyle::create(color).release_value_but_fixme_should_propagate_errors();
return m_color_paint_style.release_nonnull();
},
[&](auto handle) {
return handle->to_gfx_paint_style();
});
}
Gfx::Color CanvasState::FillOrStrokeStyle::to_color_but_fixme_should_accept_any_paint_style() const
{
return as_color().value_or(Gfx::Color::Black);
}
Optional<Gfx::Color> CanvasState::FillOrStrokeStyle::as_color() const
{
if (auto* color = m_fill_or_stroke_style.get_pointer<Gfx::Color>())
return *color;
return {};
}
}

View file

@ -0,0 +1,119 @@
/*
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Color.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/PaintStyle.h>
#include <LibGfx/Path.h>
#include <LibGfx/WindingRule.h>
#include <LibWeb/Bindings/CanvasRenderingContext2DPrototype.h>
#include <LibWeb/HTML/CanvasGradient.h>
#include <LibWeb/HTML/CanvasPattern.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvasstate
class CanvasState {
public:
virtual ~CanvasState() = default;
virtual Gfx::Painter* painter_for_canvas_state() = 0;
virtual Gfx::Path& path_for_canvas_state() = 0;
void save();
void restore();
void reset();
bool is_context_lost();
using FillOrStrokeVariant = Variant<Gfx::Color, JS::Handle<CanvasGradient>, JS::Handle<CanvasPattern>>;
struct FillOrStrokeStyle {
FillOrStrokeStyle(Gfx::Color color)
: m_fill_or_stroke_style(color)
{
}
FillOrStrokeStyle(JS::Handle<CanvasGradient> gradient)
: m_fill_or_stroke_style(gradient)
{
}
FillOrStrokeStyle(JS::Handle<CanvasPattern> pattern)
: m_fill_or_stroke_style(pattern)
{
}
NonnullRefPtr<Gfx::PaintStyle> to_gfx_paint_style();
Optional<Gfx::Color> as_color() const;
Gfx::Color to_color_but_fixme_should_accept_any_paint_style() const;
using JsFillOrStrokeStyle = Variant<String, JS::Handle<CanvasGradient>, JS::Handle<CanvasPattern>>;
JsFillOrStrokeStyle to_js_fill_or_stroke_style() const
{
return m_fill_or_stroke_style.visit(
[&](Gfx::Color color) -> JsFillOrStrokeStyle {
return color.to_string(Gfx::Color::HTMLCompatibleSerialization::Yes);
},
[&](auto handle) -> JsFillOrStrokeStyle {
return handle;
});
}
private:
FillOrStrokeVariant m_fill_or_stroke_style;
RefPtr<Gfx::PaintStyle> m_color_paint_style { nullptr };
};
// https://html.spec.whatwg.org/multipage/canvas.html#drawing-state
struct DrawingState {
Gfx::AffineTransform transform;
FillOrStrokeStyle fill_style { Gfx::Color::Black };
FillOrStrokeStyle stroke_style { Gfx::Color::Black };
float shadow_offset_x { 0.0f };
float shadow_offset_y { 0.0f };
Gfx::Color shadow_color { Gfx::Color::Transparent };
float line_width { 1 };
Bindings::CanvasLineCap line_cap { Bindings::CanvasLineCap::Butt };
Bindings::CanvasLineJoin line_join { Bindings::CanvasLineJoin::Miter };
float miter_limit { 10 };
Vector<double> dash_list;
float line_dash_offset { 0 };
bool image_smoothing_enabled { true };
Bindings::ImageSmoothingQuality image_smoothing_quality { Bindings::ImageSmoothingQuality::Low };
float global_alpha = { 1 };
RefPtr<CSS::CSSStyleValue> font_style_value { nullptr };
RefPtr<Gfx::Font const> current_font { nullptr };
Bindings::CanvasTextAlign text_align { Bindings::CanvasTextAlign::Start };
Bindings::CanvasTextBaseline text_baseline { Bindings::CanvasTextBaseline::Alphabetic };
};
DrawingState& drawing_state() { return m_drawing_state; }
DrawingState const& drawing_state() const { return m_drawing_state; }
void clear_drawing_state_stack() { m_drawing_state_stack.clear(); }
void reset_drawing_state() { m_drawing_state = {}; }
virtual void reset_to_default_state() = 0;
protected:
CanvasState() = default;
private:
DrawingState m_drawing_state;
Vector<DrawingState> m_drawing_state_stack;
// https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-context-lost
bool m_context_lost { false };
};
}

View file

@ -0,0 +1,7 @@
// https://html.spec.whatwg.org/multipage/canvas.html#canvasstate
interface mixin CanvasState {
undefined save();
undefined restore();
undefined reset();
boolean isContextLost();
};

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/Optional.h>
#include <LibWeb/HTML/TextMetrics.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvastext
class CanvasText {
public:
virtual ~CanvasText() = default;
virtual void fill_text(StringView, float x, float y, Optional<double> max_width) = 0;
virtual void stroke_text(StringView, float x, float y, Optional<double> max_width) = 0;
virtual JS::NonnullGCPtr<TextMetrics> measure_text(StringView text) = 0;
protected:
CanvasText() = default;
};
}

View file

@ -0,0 +1,8 @@
#import <HTML/TextMetrics.idl>
// https://html.spec.whatwg.org/multipage/canvas.html#canvastext
interface mixin CanvasText {
undefined fillText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth);
undefined strokeText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth);
TextMetrics measureText(DOMString text);
};

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2023, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/Canvas/CanvasState.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvastextdrawingstyles
template<typename IncludingClass>
class CanvasTextDrawingStyles {
public:
~CanvasTextDrawingStyles() = default;
ByteString font() const
{
// When font style value is empty return default string
if (!my_drawing_state().font_style_value) {
return "10px sans-serif";
}
// On getting, the font attribute must return the serialized form of the current font of the context (with no 'line-height' component).
auto const& font_style_value = my_drawing_state().font_style_value->as_shorthand();
auto font_style = font_style_value.longhand(CSS::PropertyID::FontStyle);
auto font_weight = font_style_value.longhand(CSS::PropertyID::FontWeight);
auto font_size = font_style_value.longhand(CSS::PropertyID::FontSize);
auto font_family = font_style_value.longhand(CSS::PropertyID::FontFamily);
return ByteString::formatted("{} {} {} {}", font_style->to_string(), font_weight->to_string(), font_size->to_string(), font_family->to_string());
}
void set_font(StringView font)
{
// The font IDL attribute, on setting, must be parsed as a CSS <'font'> value (but without supporting property-independent style sheet syntax like 'inherit'),
// and the resulting font must be assigned to the context, with the 'line-height' component forced to 'normal', with the 'font-size' component converted to CSS pixels,
// and with system fonts being computed to explicit values.
// FIXME: with the 'line-height' component forced to 'normal'
// FIXME: with the 'font-size' component converted to CSS pixels
auto parsing_context = CSS::Parser::ParsingContext { reinterpret_cast<IncludingClass&>(*this).realm() };
auto font_style_value_result = parse_css_value(parsing_context, font, CSS::PropertyID::Font);
// If the new value is syntactically incorrect (including using property-independent style sheet syntax like 'inherit' or 'initial'), then it must be ignored, without assigning a new font value.
// NOTE: ShorthandStyleValue should be the only valid option here. We implicitly VERIFY this below.
if (!font_style_value_result || !font_style_value_result->is_shorthand()) {
return;
}
my_drawing_state().font_style_value = font_style_value_result.release_nonnull();
// Load font with font style value properties
auto const& font_style_value = my_drawing_state().font_style_value->as_shorthand();
auto& canvas_element = reinterpret_cast<IncludingClass&>(*this).canvas_element();
auto& font_style = *font_style_value.longhand(CSS::PropertyID::FontStyle);
auto& font_weight = *font_style_value.longhand(CSS::PropertyID::FontWeight);
auto& font_width = *font_style_value.longhand(CSS::PropertyID::FontWidth);
auto& font_size = *font_style_value.longhand(CSS::PropertyID::FontSize);
auto& font_family = *font_style_value.longhand(CSS::PropertyID::FontFamily);
auto font_list = canvas_element.document().style_computer().compute_font_for_style_values(&canvas_element, {}, font_family, font_size, font_style, font_weight, font_width);
my_drawing_state().current_font = font_list->first();
}
Bindings::CanvasTextAlign text_align() const { return my_drawing_state().text_align; }
void set_text_align(Bindings::CanvasTextAlign text_align) { my_drawing_state().text_align = text_align; }
Bindings::CanvasTextBaseline text_baseline() const { return my_drawing_state().text_baseline; }
void set_text_baseline(Bindings::CanvasTextBaseline text_baseline) { my_drawing_state().text_baseline = text_baseline; }
protected:
CanvasTextDrawingStyles() = default;
private:
CanvasState::DrawingState& my_drawing_state() { return reinterpret_cast<IncludingClass&>(*this).drawing_state(); }
CanvasState::DrawingState const& my_drawing_state() const { return reinterpret_cast<IncludingClass const&>(*this).drawing_state(); }
};
}

View file

@ -0,0 +1,24 @@
#import <HTML/CanvasRenderingContext2D.idl>
// https://html.spec.whatwg.org/multipage/canvas.html#canvastextalign
// enum CanvasTextAlign { "start", "end", "left", "right", "center" };
// enum CanvasTextBaseline { "top", "hanging", "middle", "alphabetic", "ideographic", "bottom" };
enum CanvasDirection { "ltr", "rtl", "inherit" };
enum CanvasFontKerning { "auto", "normal", "none" };
enum CanvasFontStretch { "ultra-condensed", "extra-condensed", "condensed", "semi-condensed", "normal", "semi-expanded", "expanded", "extra-expanded", "ultra-expanded" };
enum CanvasFontVariantCaps { "normal", "small-caps", "all-small-caps", "petite-caps", "all-petite-caps", "unicase", "titling-caps" };
enum CanvasTextRendering { "auto", "optimizeSpeed", "optimizeLegibility", "geometricPrecision" };
// https://html.spec.whatwg.org/multipage/canvas.html#canvastextdrawingstyles
interface mixin CanvasTextDrawingStyles {
attribute DOMString font; // (default 10px sans-serif)
attribute CanvasTextAlign textAlign; // (default: "start")
attribute CanvasTextBaseline textBaseline; // (default: "alphabetic")
[FIXME] attribute CanvasDirection direction; // (default: "inherit")
[FIXME] attribute DOMString letterSpacing; // (default: "0px")
[FIXME] attribute CanvasFontKerning fontKerning; // (default: "auto")
[FIXME] attribute CanvasFontStretch fontStretch; // (default: "normal")
[FIXME] attribute CanvasFontVariantCaps fontVariantCaps; // (default: "normal")
[FIXME] attribute CanvasTextRendering textRendering; // (default: "auto")
[FIXME] attribute DOMString wordSpacing; // (default: "0px")
};

View file

@ -0,0 +1,146 @@
/*
* Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Debug.h>
#include <LibGfx/Painter.h>
#include <LibWeb/Geometry/DOMMatrix.h>
#include <LibWeb/HTML/Canvas/CanvasPath.h>
#include <LibWeb/HTML/Canvas/CanvasState.h>
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/canvas.html#canvastransform
template<typename IncludingClass>
class CanvasTransform {
public:
~CanvasTransform() = default;
Gfx::Path& mutable_path() { return static_cast<IncludingClass&>(*this).path(); }
void scale(float sx, float sy)
{
dbgln_if(CANVAS_RENDERING_CONTEXT_2D_DEBUG, "CanvasTransform::scale({}, {})", sx, sy);
if (!isfinite(sx) || !isfinite(sy))
return;
my_drawing_state().transform.scale(sx, sy);
flush_transform();
mutable_path().transform(Gfx::AffineTransform().scale(1.0 / sx, 1.0 / sy));
}
void translate(float tx, float ty)
{
dbgln_if(CANVAS_RENDERING_CONTEXT_2D_DEBUG, "CanvasTransform::translate({}, {})", tx, ty);
if (!isfinite(tx) || !isfinite(ty))
return;
my_drawing_state().transform.translate(tx, ty);
flush_transform();
mutable_path().transform(Gfx::AffineTransform().translate(-tx, -ty));
}
void rotate(float radians)
{
dbgln_if(CANVAS_RENDERING_CONTEXT_2D_DEBUG, "CanvasTransform::rotate({})", radians);
if (!isfinite(radians))
return;
my_drawing_state().transform.rotate_radians(radians);
flush_transform();
mutable_path().transform(Gfx::AffineTransform().rotate_radians(-radians));
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-transform
void transform(double a, double b, double c, double d, double e, double f)
{
// 1. If any of the arguments are infinite or NaN, then return.
if (!isfinite(a) || !isfinite(b) || !isfinite(c) || !isfinite(d) || !isfinite(e) || !isfinite(f))
return;
// 2. Replace the current transformation matrix with the result of multiplying the current transformation matrix with the matrix described by:
// a c e
// b d f
// 0 0 1
auto transform = Gfx::AffineTransform(a, b, c, d, e, f);
my_drawing_state().transform.multiply(transform);
if (auto inverse = transform.inverse(); inverse.has_value()) {
mutable_path().transform(inverse.value());
}
flush_transform();
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-gettransform
WebIDL::ExceptionOr<JS::NonnullGCPtr<Geometry::DOMMatrix>> get_transform()
{
auto& realm = static_cast<IncludingClass&>(*this).realm();
auto transform = my_drawing_state().transform;
Geometry::DOMMatrix2DInit init = { transform.a(), transform.b(), transform.c(), transform.d(), transform.e(), transform.f(), {}, {}, {}, {}, {}, {} };
return Geometry::DOMMatrix::create_from_dom_matrix_2d_init(realm, init);
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-settransform
void set_transform(double a, double b, double c, double d, double e, double f)
{
// 1. If any of the arguments are infinite or NaN, then return.
if (!isfinite(a) || !isfinite(b) || !isfinite(c) || !isfinite(d) || !isfinite(e) || !isfinite(f))
return;
// 2. Reset the current transformation matrix to the identity matrix.
my_drawing_state().transform = {};
flush_transform();
// 3. Invoke the transform(a, b, c, d, e, f) method with the same arguments.
transform(a, b, c, d, e, f);
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-settransform-matrix
WebIDL::ExceptionOr<void> set_transform(Geometry::DOMMatrix2DInit& init)
{
// 1. Let matrix be the result of creating a DOMMatrix from the 2D dictionary transform.
auto& realm = static_cast<IncludingClass&>(*this).realm();
auto matrix = TRY(Geometry::DOMMatrix::create_from_dom_matrix_2d_init(realm, init));
// 2. If one or more of matrix's m11 element, m12 element, m21 element, m22 element, m41 element, or m42 element are infinite or NaN, then return.
if (!isfinite(matrix->m11()) || !isfinite(matrix->m12()) || !isfinite(matrix->m21()) || !isfinite(matrix->m22()) || !isfinite(matrix->m41()) || !isfinite(matrix->m42()))
return {};
auto original_transform = my_drawing_state().transform;
// 3. Reset the current transformation matrix to matrix.
auto transform = Gfx::AffineTransform { static_cast<float>(matrix->a()), static_cast<float>(matrix->b()), static_cast<float>(matrix->c()), static_cast<float>(matrix->d()), static_cast<float>(matrix->e()), static_cast<float>(matrix->f()) };
my_drawing_state().transform = transform;
mutable_path().transform(original_transform);
flush_transform();
return {};
}
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-resettransform
void reset_transform()
{
// The resetTransform() method, when invoked, must reset the current transformation matrix to the identity matrix.
my_drawing_state().transform = {};
flush_transform();
}
void flush_transform()
{
if (auto* painter = static_cast<IncludingClass&>(*this).painter())
painter->set_transform(my_drawing_state().transform);
}
protected:
CanvasTransform() = default;
private:
CanvasState::DrawingState& my_drawing_state() { return reinterpret_cast<IncludingClass&>(*this).drawing_state(); }
CanvasState::DrawingState const& my_drawing_state() const { return reinterpret_cast<IncludingClass const&>(*this).drawing_state(); }
};
}

View file

@ -0,0 +1,14 @@
#import <Geometry/DOMMatrix.idl>
// https://html.spec.whatwg.org/multipage/canvas.html#canvastransform
interface mixin CanvasTransform {
undefined scale(unrestricted double x, unrestricted double y);
undefined rotate(unrestricted double radians);
undefined translate(unrestricted double x, unrestricted double y);
undefined transform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f);
[NewObject] DOMMatrix getTransform();
undefined setTransform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f);
undefined setTransform(optional DOMMatrix2DInit transform = {});
undefined resetTransform();
};

View file

@ -0,0 +1,5 @@
// https://html.spec.whatwg.org/multipage/canvas.html#canvasuserinterface
interface mixin CanvasUserInterface {
[FIXME] undefined drawFocusIfNeeded(Element element);
[FIXME] undefined drawFocusIfNeeded(Path2D path, Element element);
};