mirror of
https://github.com/SerenityOS/serenity
synced 2024-07-22 02:26:11 +00:00
LibWeb: Use paths for text in CRC2D (if possible)
This allows for: * Transformed text (e.g. rotated text) * Stroked text * Filling/stroking text with PaintStyles (e.g. gradients) * Squashed/condensed text (via maxWidth parameter) Fixes part of #22817
This commit is contained in:
parent
774119bb57
commit
f19b17e089
46
Tests/LibWeb/Ref/canvas-text.html
Normal file
46
Tests/LibWeb/Ref/canvas-text.html
Normal file
|
@ -0,0 +1,46 @@
|
|||
<link rel="match" href="reference/canvas-text-ref.html" />
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
canvas {
|
||||
border: 1px solid black;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = 600;
|
||||
canvas.height = 280;
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.font = "48px serif";
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(20, 250);
|
||||
ctx.rotate(-Math.PI*0.2);
|
||||
ctx.fillText("Rotated Text!", 10, 40);
|
||||
ctx.restore();
|
||||
|
||||
ctx.strokeText("Stroke Text!", 10, 50);
|
||||
|
||||
const gradient = ctx.createLinearGradient(280, 20, 580, 120);
|
||||
gradient.addColorStop(0,"red");
|
||||
gradient.addColorStop(0.15,"yellow");
|
||||
gradient.addColorStop(0.3,"green");
|
||||
gradient.addColorStop(0.45,"aqua");
|
||||
gradient.addColorStop(0.6,"blue");
|
||||
gradient.addColorStop(0.7,"fuchsia");
|
||||
gradient.addColorStop(1,"red");
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillText("Gradient Text!", 260, 150);
|
||||
|
||||
ctx.fillStyle = "red";
|
||||
ctx.fillText("Squished Text", 50, 120, 100);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
9
Tests/LibWeb/Ref/reference/canvas-text-ref.html
Normal file
9
Tests/LibWeb/Ref/reference/canvas-text-ref.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
||||
<img src="./images/canvas-text-ref.png">
|
BIN
Tests/LibWeb/Ref/reference/images/canvas-text-ref.png
Normal file
BIN
Tests/LibWeb/Ref/reference/images/canvas-text-ref.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -198,7 +198,7 @@ Optional<Gfx::AntiAliasingPainter> CanvasRenderingContext2D::antialiased_painter
|
|||
return {};
|
||||
}
|
||||
|
||||
void CanvasRenderingContext2D::fill_text(StringView text, float x, float y, Optional<double> max_width)
|
||||
void CanvasRenderingContext2D::bitmap_font_fill_text(StringView text, float x, float y, Optional<double> 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<float>(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<double> 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<double> max_width)
|
||||
{
|
||||
if (is<Gfx::BitmapFont>(*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<double> max_width)
|
||||
{
|
||||
// FIXME: Stroke the text instead of filling it.
|
||||
fill_text(text, x, y, max_width);
|
||||
if (is<Gfx::BitmapFont>(*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()
|
||||
|
|
|
@ -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<double> max_width);
|
||||
void bitmap_font_fill_text(StringView text, float x, float y, Optional<double> 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);
|
||||
|
|
Loading…
Reference in a new issue