LibWeb: Hook up canvas lineCap and plumb it to LibGfx

To rebaseline image test expecatations, I ran:

    out/gn/Ladybird.app/Contents/MacOS/headless-browser \
        --resources $PWD/out/gn/Ladybird.app/Contents/Resources \
        --dump-failed-ref-tests \
        --run-tests $PWD/Tests/LibWeb \
        --filter 'canvas-*'

I then copied over the new baselines with

    D=Tests/LibWeb/Ref/reference/images
    cp test-dumps/canvas-implict-moves-and-lines.png \
        $D/canvas-implict-moves-and-lines-ref.png

(Note: No `-ref` suffix on first path, yes suffix on second path.)

We currently don't track if a path is open or closed, and paint
butt linecaps at the end of closed paths too. We did that with
round linecaps as well, but there that wasn't visible. This makes
closed paths look a bit weird now; we'll have to fix this in a
follow-up. In a way, this just exposes another not-yet-implemented
feature.
This commit is contained in:
Nico Weber 2024-09-24 22:21:24 -04:00
parent 7f528a4fa6
commit c30188cd6a
11 changed files with 37 additions and 8 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 999 B

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -192,12 +192,12 @@ void AntiAliasingPainter::draw_line(FloatPoint actual_from, FloatPoint actual_to
draw_anti_aliased_line(actual_from, actual_to, color, thickness, style, alternate_color, line_length_mode);
}
void AntiAliasingPainter::stroke_path(Path const& path, Color color, float thickness)
void AntiAliasingPainter::stroke_path(Path const& path, Color color, float thickness, Path::CapStyle cap_style)
{
if (thickness <= 0)
return;
// FIXME: Cache this? Probably at a higher level such as in LibWeb?
fill_path(path.stroke_to_fill(thickness), color);
fill_path(path.stroke_to_fill(thickness, cap_style), color);
}
void AntiAliasingPainter::stroke_path(Path const& path, Gfx::PaintStyle const& paint_style, float thickness, float opacity, Path::CapStyle cap_style)

View file

@ -37,7 +37,7 @@ public:
void fill_path(Path const&, Color, Painter::WindingRule rule = Painter::WindingRule::Nonzero);
void fill_path(Path const&, PaintStyle const& paint_style, float opacity = 1.0f, Painter::WindingRule rule = Painter::WindingRule::Nonzero);
void stroke_path(Path const&, Color, float thickness);
void stroke_path(Path const&, Color, float thickness, Path::CapStyle cap_style = Path::CapStyle::Round);
void stroke_path(Path const&, PaintStyle const& paint_style, float thickness, float opacity = 1.0f, Path::CapStyle cap_style = Path::CapStyle::Round);
void translate(float dx, float dy) { m_transform.translate(dx, dy); }

View file

@ -31,6 +31,18 @@ public:
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;
}
protected:
CanvasPathDrawingStyles() = default;

View file

@ -1,11 +1,11 @@
// https://html.spec.whatwg.org/multipage/canvas.html#canvaslinecap
enum CanvasLineCap { "butt", "round", "square" };
enum CanvasLineJoin { "round", "bevel", "miter" };
// 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;
[FIXME] attribute CanvasLineCap lineCap;
attribute CanvasLineCap lineCap;
[FIXME] attribute CanvasLineJoin lineJoin;
[FIXME] attribute unrestricted double miterLimit;

View file

@ -76,6 +76,7 @@ public:
FillOrStrokeStyle fill_style { Gfx::Color::Black };
FillOrStrokeStyle stroke_style { Gfx::Color::Black };
float line_width { 1 };
Bindings::CanvasLineCap line_cap { Bindings::CanvasLineCap::Butt };
bool image_smoothing_enabled { true };
Bindings::ImageSmoothingQuality image_smoothing_quality { Bindings::ImageSmoothingQuality::Low };
float global_alpha = { 1 };

View file

@ -319,10 +319,22 @@ void CanvasRenderingContext2D::stroke_internal(Gfx::Path const& path)
{
draw_clipped([&](auto& painter) {
auto& drawing_state = this->drawing_state();
Gfx::Path::CapStyle line_cap = [](Bindings::CanvasLineCap cap) {
switch (cap) {
case Bindings::CanvasLineCap::Butt:
return Gfx::Path::CapStyle::Butt;
case Bindings::CanvasLineCap::Round:
return Gfx::Path::CapStyle::Round;
case Bindings::CanvasLineCap::Square:
// FIXME: Use LineCapStyle::Square once implemented.
return Gfx::Path::CapStyle::Round;
}
VERIFY_NOT_REACHED();
}(drawing_state.line_cap);
if (auto color = drawing_state.stroke_style.as_color(); color.has_value()) {
painter.stroke_path(path, color->with_opacity(drawing_state.global_alpha), drawing_state.line_width);
painter.stroke_path(path, color->with_opacity(drawing_state.global_alpha), drawing_state.line_width, line_cap);
} else {
painter.stroke_path(path, drawing_state.stroke_style.to_gfx_paint_style(), drawing_state.line_width, drawing_state.global_alpha);
painter.stroke_path(path, drawing_state.stroke_style.to_gfx_paint_style(), drawing_state.line_width, drawing_state.global_alpha, line_cap);
}
return path.bounding_box();
});

View file

@ -15,6 +15,10 @@
enum ImageSmoothingQuality { "low", "medium", "high" };
// FIXME: This should be in CanvasPathDrawingStyles.idl but then it is not exported
enum CanvasLineCap { "butt", "round", "square" };
enum CanvasLineJoin { "round", "bevel", "miter" };
// FIXME: This should be in CanvasTextDrawingStyles.idl but then it is not exported
enum CanvasTextAlign { "start", "end", "left", "right", "center" };
enum CanvasTextBaseline { "top", "hanging", "middle", "alphabetic", "ideographic", "bottom" };