diff --git a/Tests/LibWeb/Ref/canvas-text.html b/Tests/LibWeb/Ref/canvas-text.html
new file mode 100644
index 0000000000..d29efeffac
--- /dev/null
+++ b/Tests/LibWeb/Ref/canvas-text.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
diff --git a/Tests/LibWeb/Ref/reference/canvas-text-ref.html b/Tests/LibWeb/Ref/reference/canvas-text-ref.html
new file mode 100644
index 0000000000..968b109850
--- /dev/null
+++ b/Tests/LibWeb/Ref/reference/canvas-text-ref.html
@@ -0,0 +1,9 @@
+
+
diff --git a/Tests/LibWeb/Ref/reference/images/canvas-text-ref.png b/Tests/LibWeb/Ref/reference/images/canvas-text-ref.png
new file mode 100644
index 0000000000..0684ebcba5
Binary files /dev/null and b/Tests/LibWeb/Ref/reference/images/canvas-text-ref.png differ
diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
index 7d769c20d0..229497a219 100644
--- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
+++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp
@@ -198,7 +198,7 @@ Optional CanvasRenderingContext2D::antialiased_painter
return {};
}
-void CanvasRenderingContext2D::fill_text(StringView text, float x, float y, Optional max_width)
+void CanvasRenderingContext2D::bitmap_font_fill_text(StringView text, float x, float y, Optional max_width)
{
if (max_width.has_value() && max_width.value() <= 0)
return;
@@ -207,9 +207,8 @@ void CanvasRenderingContext2D::fill_text(StringView text, float x, float y, Opti
auto& drawing_state = this->drawing_state();
auto& base_painter = painter.underlying_painter();
- auto font = current_font();
-
// Create text rect from font
+ auto font = current_font();
auto text_rect = Gfx::FloatRect(x, y, max_width.has_value() ? static_cast(max_width.value()) : font->width(text), font->pixel_size());
// Apply text align to text_rect
@@ -242,10 +241,72 @@ void CanvasRenderingContext2D::fill_text(StringView text, float x, float y, Opti
});
}
+Gfx::Path CanvasRenderingContext2D::text_path(StringView text, float x, float y, Optional max_width)
+{
+ if (max_width.has_value() && max_width.value() <= 0)
+ return {};
+
+ auto& drawing_state = this->drawing_state();
+ auto font = current_font();
+
+ Gfx::Path path;
+ path.move_to({ x, y });
+ path.text(Utf8View { text }, *font);
+
+ auto text_width = path.bounding_box().width();
+ Gfx::AffineTransform transform = {};
+
+ // https://html.spec.whatwg.org/multipage/canvas.html#text-preparation-algorithm:
+ // 6. If maxWidth was provided and the hypothetical width of the inline box in the hypothetical line box
+ // is greater than maxWidth CSS pixels, then change font to have a more condensed font (if one is
+ // available or if a reasonably readable one can be synthesized by applying a horizontal scale
+ // factor to the font) or a smaller font, and return to the previous step.
+ if (max_width.has_value() && text_width > float(*max_width)) {
+ auto horizontal_scale = float(*max_width) / text_width;
+ transform = Gfx::AffineTransform {}.scale({ horizontal_scale, 1 });
+ text_width *= horizontal_scale;
+ }
+
+ // Apply text align
+ // FIXME: CanvasTextAlign::Start and CanvasTextAlign::End currently do not nothing for right-to-left languages:
+ // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-textalign-start
+ // Default alignment of draw_text is left so do nothing by CanvasTextAlign::Start and CanvasTextAlign::Left
+ if (drawing_state.text_align == Bindings::CanvasTextAlign::Center) {
+ transform = Gfx::AffineTransform {}.set_translation({ -text_width / 2, 0 }).multiply(transform);
+ }
+ if (drawing_state.text_align == Bindings::CanvasTextAlign::End || drawing_state.text_align == Bindings::CanvasTextAlign::Right) {
+ transform = Gfx::AffineTransform {}.set_translation({ -text_width, 0 }).multiply(transform);
+ }
+
+ // Apply text baseline
+ // FIXME: Implement CanvasTextBasline::Hanging, Bindings::CanvasTextAlign::Alphabetic and Bindings::CanvasTextAlign::Ideographic for real
+ // right now they are just handled as textBaseline = top or bottom.
+ // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-textbaseline-hanging
+ // Default baseline of draw_text is top so do nothing by CanvasTextBaseline::Top and CanvasTextBasline::Hanging
+ if (drawing_state.text_baseline == Bindings::CanvasTextBaseline::Middle) {
+ transform = Gfx::AffineTransform {}.set_translation({ 0, font->pixel_size() / 2 }).multiply(transform);
+ }
+ if (drawing_state.text_baseline == Bindings::CanvasTextBaseline::Top || drawing_state.text_baseline == Bindings::CanvasTextBaseline::Hanging) {
+ transform = Gfx::AffineTransform {}.set_translation({ 0, font->pixel_size() }).multiply(transform);
+ }
+
+ transform = Gfx::AffineTransform { drawing_state.transform }.multiply(transform);
+ path = path.copy_transformed(transform);
+ return path;
+}
+
+void CanvasRenderingContext2D::fill_text(StringView text, float x, float y, Optional max_width)
+{
+ if (is(*current_font()))
+ return bitmap_font_fill_text(text, x, y, max_width);
+ fill_internal(text_path(text, x, y, max_width), Gfx::Painter::WindingRule::Nonzero);
+}
+
void CanvasRenderingContext2D::stroke_text(StringView text, float x, float y, Optional max_width)
{
- // FIXME: Stroke the text instead of filling it.
- fill_text(text, x, y, max_width);
+ if (is(*current_font()))
+ return bitmap_font_fill_text(text, x, y, max_width);
+ stroke_internal(text_path(text, x, y, max_width));
}
void CanvasRenderingContext2D::begin_path()
diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h
index cce9f5a3f4..7bfc536a7b 100644
--- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h
+++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.h
@@ -145,6 +145,9 @@ private:
Gfx::Path rect_path(float x, float y, float width, float height);
+ Gfx::Path text_path(StringView text, float x, float y, Optional max_width);
+ void bitmap_font_fill_text(StringView text, float x, float y, Optional max_width);
+
void stroke_internal(Gfx::Path const&);
void fill_internal(Gfx::Path const&, Gfx::Painter::WindingRule);
void clip_internal(Gfx::Path&, Gfx::Painter::WindingRule);