Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions Libraries/LibGfx/Path.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ class PathImpl {
virtual void arc_to(FloatPoint point, float radius, bool large_arc, bool sweep) = 0;
virtual void quadratic_bezier_curve_to(FloatPoint through, FloatPoint point) = 0;
virtual void cubic_bezier_curve_to(FloatPoint c1, FloatPoint c2, FloatPoint p2) = 0;
virtual void text(Utf8View const&, Font const&) = 0;
virtual void text(Utf16View const&, Font const&) = 0;
virtual void glyph_run(GlyphRun const&) = 0;
virtual void offset(Gfx::FloatPoint const&) = 0;

Expand All @@ -53,8 +51,7 @@ class PathImpl {

virtual NonnullOwnPtr<PathImpl> clone() const = 0;
virtual NonnullOwnPtr<PathImpl> copy_transformed(Gfx::AffineTransform const&) const = 0;
virtual NonnullOwnPtr<PathImpl> place_text_along(Utf8View const& text, Font const&, float offset = 0) const = 0;
virtual NonnullOwnPtr<PathImpl> place_text_along(Utf16View const& text, Font const&, float offset = 0) const = 0;
virtual NonnullOwnPtr<PathImpl> place_glyph_runs_along(ReadonlySpan<NonnullRefPtr<GlyphRun>>, float offset = 0) const = 0;

virtual String to_svg_string() const = 0;
};
Expand Down Expand Up @@ -101,8 +98,6 @@ class Path {
void arc_to(FloatPoint point, float radius, bool large_arc, bool sweep) { impl().arc_to(point, radius, large_arc, sweep); }
void quadratic_bezier_curve_to(FloatPoint through, FloatPoint point) { impl().quadratic_bezier_curve_to(through, point); }
void cubic_bezier_curve_to(FloatPoint c1, FloatPoint c2, FloatPoint p2) { impl().cubic_bezier_curve_to(c1, c2, p2); }
void text(Utf8View const& text, Font const& font) { impl().text(text, font); }
void text(Utf16View const& text, Font const& font) { impl().text(text, font); }
void glyph_run(GlyphRun const& glyph_run) { impl().glyph_run(glyph_run); }
void offset(Gfx::FloatPoint const& offset) { impl().offset(offset); }

Expand All @@ -123,8 +118,7 @@ class Path {

Gfx::Path clone() const { return Gfx::Path { impl().clone() }; }
Gfx::Path copy_transformed(Gfx::AffineTransform const& transform) const { return Gfx::Path { impl().copy_transformed(transform) }; }
Gfx::Path place_text_along(Utf8View const& text, Font const& font, float offset = 0) const { return Gfx::Path { impl().place_text_along(text, font, offset) }; }
Gfx::Path place_text_along(Utf16View const& text, Font const& font, float offset = 0) const { return Gfx::Path { impl().place_text_along(text, font, offset) }; }
Gfx::Path place_glyph_runs_along(ReadonlySpan<NonnullRefPtr<GlyphRun>> glyph_runs, float offset = 0) const { return Gfx::Path { impl().place_glyph_runs_along(glyph_runs, offset) }; }

String to_svg_string() const { return impl().to_svg_string(); }

Expand Down
111 changes: 30 additions & 81 deletions Libraries/LibGfx/PathSkia.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@

#include <AK/Span.h>
#include <AK/TypeCasts.h>
#include <AK/Utf16View.h>
#include <AK/Utf8View.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/PathSkia.h>
#include <LibGfx/Rect.h>
Expand All @@ -19,10 +17,8 @@
#include <core/SkPath.h>
#include <core/SkPathBuilder.h>
#include <core/SkPathMeasure.h>
#include <core/SkTextBlob.h>
#include <pathops/SkPathOps.h>
#include <utils/SkParsePath.h>
#include <utils/SkTextUtils.h>

namespace Gfx {

Expand Down Expand Up @@ -181,25 +177,6 @@ void PathImplSkia::cubic_bezier_curve_to(FloatPoint c1, FloatPoint c2, FloatPoin
sk_path_builder().cubicTo(c1.x(), c1.y(), c2.x(), c2.y(), p2.x(), p2.y());
}

void PathImplSkia::text(Utf8View const& string, Font const& font)
{
SkPath text_path;
SkTextUtils::GetPath(string.as_string().characters_without_null_termination(), string.as_string().length(), SkTextEncoding::kUTF8, last_point().x(), last_point().y(), font.skia_font(1), &text_path);
set_path(text_path);
}

void PathImplSkia::text(Utf16View const& string, Font const& font)
{
if (string.has_ascii_storage()) {
text(Utf8View { string.bytes() }, font);
return;
}

SkPath text_path;
SkTextUtils::GetPath(string.utf16_span().data(), string.length_in_code_units() * sizeof(char16_t), SkTextEncoding::kUTF16, last_point().x(), last_point().y(), font.skia_font(1), &text_path);
set_path(text_path);
}

void PathImplSkia::glyph_run(GlyphRun const& glyph_run)
{
auto sk_font = glyph_run.font().skia_font(1);
Expand All @@ -222,75 +199,47 @@ void PathImplSkia::offset(Gfx::FloatPoint const& offset)
m_last_move_to.translate_by(offset);
}

template<typename TextToGlyphs>
static NonnullOwnPtr<PathImpl> place_text_along_impl(SkPath const& path, Font const& font, size_t length_in_code_points, float offset, TextToGlyphs&& text_to_glyphs)
NonnullOwnPtr<PathImpl> PathImplSkia::place_glyph_runs_along(ReadonlySpan<NonnullRefPtr<GlyphRun>> glyph_runs, float offset) const
{
auto sk_font = font.skia_font(1);
SkScalar x = 0;
SkScalar y = 0;

SkTextBlobBuilder builder;
auto const& run_buffer = builder.allocRun(sk_font, static_cast<int>(length_in_code_points), x, y, nullptr);
text_to_glyphs(sk_font, run_buffer);

SkPathMeasure path_measure(path, false);
SkScalar accumulated_distance = offset;

auto output_path = PathImplSkia::create();
SkPathMeasure path_measure(sk_path(), false);
SkScalar path_length = path_measure.getLength();

for (size_t i = 0; i < length_in_code_points; ++i) {
SkGlyphID glyph = run_buffer.glyphs[i];
auto glyph_path = sk_font.getPath(glyph);

SkScalar advance = 0;
sk_font.getWidths({ &glyph, 1 }, { &advance, 1 });

SkPoint position;
SkVector tangent;
if (!path_measure.getPosTan(accumulated_distance, &position, &tangent))
continue;

// Any typographic characters with mid-points that are not on the path are not rendered.
SkScalar midpoint_distance = accumulated_distance + (advance / 2.0f);
if (midpoint_distance > path_length)
break;

SkMatrix matrix;
matrix.setTranslate(position.x(), position.y());
matrix.preRotate(SkRadiansToDegrees(std::atan2(tangent.y(), tangent.x())));
auto output_path = PathImplSkia::create();

if (glyph_path.has_value())
bool reached_end_of_path = false;
for (auto const& glyph_run : glyph_runs) {
auto sk_font = glyph_run->font().skia_font(1);
for (auto const& glyph : glyph_run->glyphs()) {
SkScalar glyph_distance = offset + glyph.position.x();

SkPoint position;
SkVector tangent;
if (!path_measure.getPosTan(glyph_distance, &position, &tangent))
continue;

SkScalar midpoint_distance = glyph_distance + (glyph.glyph_width / 2.0f);
if (midpoint_distance > path_length) {
reached_end_of_path = true;
break;
}

auto glyph_path = sk_font.getPath(static_cast<SkGlyphID>(glyph.glyph_id));
if (!glyph_path.has_value())
continue;

SkMatrix matrix;
matrix.setTranslate(position.x(), position.y());
matrix.preRotate(SkRadiansToDegrees(std::atan2(tangent.y(), tangent.x())));
output_path->sk_path_builder().addPath(*glyph_path, matrix);

accumulated_distance += advance;
}
if (reached_end_of_path)
break;
}
output_path->update_state_from_builder();

return output_path;
}

NonnullOwnPtr<PathImpl> PathImplSkia::place_text_along(Utf8View const& text, Font const& font, float offset) const
{
auto length_in_code_points = text.length();

return place_text_along_impl(sk_path(), font, length_in_code_points, offset, [&](auto const& sk_font, auto const& run_buffer) {
sk_font.textToGlyphs(text.as_string().characters_without_null_termination(), text.as_string().length(), SkTextEncoding::kUTF8, { run_buffer.glyphs, length_in_code_points });
});
}

NonnullOwnPtr<PathImpl> PathImplSkia::place_text_along(Utf16View const& text, Font const& font, float offset) const
{
if (text.has_ascii_storage())
return place_text_along(Utf8View { text.bytes() }, font, offset);

auto length_in_code_points = text.length_in_code_points();

return place_text_along_impl(sk_path(), font, length_in_code_points, offset, [&](auto const& sk_font, auto const& run_buffer) {
sk_font.textToGlyphs(text.utf16_span().data(), text.length_in_code_units() * sizeof(char16_t), SkTextEncoding::kUTF16, { run_buffer.glyphs, length_in_code_points });
});
}

void PathImplSkia::append_path(Gfx::Path const& other)
{
auto const& other_impl = static_cast<PathImplSkia const&>(other.impl());
Expand Down
5 changes: 1 addition & 4 deletions Libraries/LibGfx/PathSkia.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ class PathImplSkia final : public PathImpl {
virtual void arc_to(FloatPoint point, float radius, bool large_arc, bool sweep) override;
virtual void quadratic_bezier_curve_to(FloatPoint through, FloatPoint point) override;
virtual void cubic_bezier_curve_to(FloatPoint c1, FloatPoint c2, FloatPoint p2) override;
virtual void text(Utf8View const&, Font const&) override;
virtual void text(Utf16View const&, Font const&) override;
virtual void glyph_run(GlyphRun const&) override;
virtual void offset(Gfx::FloatPoint const&) override;

Expand All @@ -48,8 +46,7 @@ class PathImplSkia final : public PathImpl {

virtual NonnullOwnPtr<PathImpl> clone() const override;
virtual NonnullOwnPtr<PathImpl> copy_transformed(Gfx::AffineTransform const&) const override;
virtual NonnullOwnPtr<PathImpl> place_text_along(Utf8View const& text, Font const&, float offset = 0) const override;
virtual NonnullOwnPtr<PathImpl> place_text_along(Utf16View const& text, Font const&, float offset = 0) const override;
virtual NonnullOwnPtr<PathImpl> place_glyph_runs_along(ReadonlySpan<NonnullRefPtr<GlyphRun>>, float offset = 0) const override;

virtual String to_svg_string() const override;

Expand Down
26 changes: 15 additions & 11 deletions Libraries/LibWeb/Layout/LayoutRustBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,11 @@ static Utf16String rendered_svg_text_contents(SVG::SVGTextContentElement const&
// The advance of the text run rendered by the given box; that is, of its direct child text content.
static float svg_text_run_advance(Box const& text_box)
{
// FIXME: Use per-code-point fonts.
return text_box.first_available_font().width(static_cast<SVG::SVGTextContentElement const&>(*text_box.dom_node()).text_contents());
auto text_contents = static_cast<SVG::SVGTextContentElement const&>(*text_box.dom_node()).text_contents();
float advance = 0;
for (auto const& glyph_run : Gfx::shape_text({}, text_contents, text_box.font_list()))
advance += glyph_run->width();
return advance;
}

// https://svgwg.org/svg2-draft/text.html#TermTextChunk
Expand Down Expand Up @@ -451,17 +454,17 @@ static SvgTextChunkMeasurement measure_svg_text_chunk(Box const& chunk_start_box
static Gfx::Path compute_path_for_svg_text(Box const& text_box, Gfx::FloatPoint current_text_position)
{
auto const& text_element = static_cast<SVG::SVGTextContentElement const&>(*text_box.dom_node());
// FIXME: Use per-code-point fonts.
auto& font = text_box.first_available_font();
auto text_contents = text_element.text_contents();

auto text_offset = current_text_position;
auto baseline_metric = resolve_dominant_baseline_metric(text_box);
text_offset.translate_by(0, dominant_baseline_offset(baseline_metric, font.pixel_metrics()));
// NB: The dominant-baseline offset is resolved against the metrics of the first available font — while each glyph
// is rendered with the first font in the cascade that contains its code point.
text_offset.translate_by(0, dominant_baseline_offset(baseline_metric, text_box.first_available_font().pixel_metrics()));

Gfx::Path path;
path.move_to(text_offset);
path.text(text_contents, font);
for (auto const& glyph_run : Gfx::shape_text(text_offset, text_contents, text_box.font_list()))
path.glyph_run(glyph_run);
return path;
}

Expand All @@ -472,15 +475,16 @@ static Gfx::Path compute_path_for_svg_text_path(Box const& text_path_box, CSSPix
if (!path_or_shape)
return {};

// FIXME: Use per-code-point fonts.
auto& font = text_path_box.first_available_font();
auto text_contents = rendered_svg_text_contents(text_path_element);
auto glyph_runs = Gfx::shape_text({}, text_contents, text_path_box.font_list());

auto shape_path = const_cast<SVG::SVGGeometryElement&>(*path_or_shape).get_path(viewport_size);
auto start_offset = text_path_element.start_offset_for_path_length(shape_path.length());

// FIXME: Take writing mode and text direction into account.
auto total_advance = font.width(text_contents);
float total_advance = 0;
for (auto const& glyph_run : glyph_runs)
total_advance += glyph_run->width();
switch (text_path_element.text_anchor().value_or(SVG::TextAnchor::Start)) {
case SVG::TextAnchor::Start:
break;
Expand All @@ -494,7 +498,7 @@ static Gfx::Path compute_path_for_svg_text_path(Box const& text_path_box, CSSPix
VERIFY_NOT_REACHED();
}

return shape_path.place_text_along(text_contents, font, start_offset);
return shape_path.place_glyph_runs_along(glyph_runs, start_offset);
}

static RustFFI::FfiSvgPathResult compute_svg_path(NodeWithStyle const& node, RustFFI::FfiSvgPathRequest const& request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ Viewport <#document> at [0,0] [0+0+0 800 0+0+0] [0+0+0 600 0+0+0] [BFC] children
" "
frag 2 from BlockContainer start: 0, length: 0, rect: [319,127 102x102] baseline: 34
SVGSVGBox <svg> at [9,9] [0+1+0 300 0+1+0] [0+1+0 150 0+1+0] viewport-transform=[1 0 0 1 0 0] [SVG] children: not-inline
SVGGraphicsBox <a> at [24.765625,23.4375] [0+0+0 188.71875 0+0+0] [0+0+0 60.15625 0+0+0] children: not-inline
SVGTextBox <text> at [24.765625,23.4375] [0+0+0 188.71875 0+0+0] [0+0+0 60.15625 0+0+0] children: inline
SVGGraphicsBox <a> at [24.765625,23.4375] [0+0+0 188.828125 0+0+0] [0+0+0 60.15625 0+0+0] children: not-inline
SVGTextBox <text> at [24.765625,23.4375] [0+0+0 188.828125 0+0+0] [0+0+0 60.15625 0+0+0] children: inline
TextNode <#text> (not painted)
TextNode <#text> (not painted)
BlockContainer <math> at [319,127] [0+1+0 102 0+1+0] [0+1+0 102 0+1+0] [BFC] children: not-inline
Expand All @@ -30,8 +30,8 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x264]
PaintableWithLines (BlockContainer(anonymous)) [8,8 784x222]
SVGSVGPaintable (SVGSVGBox<svg>) [8,8 302x152]
SVGGraphicsPaintable (SVGGraphicsBox<a>) [24.765625,23.4375 188.71875x60.15625]
SVGPathPaintable (SVGTextBox<text>) [24.765625,23.4375 188.71875x60.15625]
SVGGraphicsPaintable (SVGGraphicsBox<a>) [24.765625,23.4375 188.828125x60.15625]
SVGPathPaintable (SVGTextBox<text>) [24.765625,23.4375 188.828125x60.15625]
PaintableWithLines (BlockContainer<math>) [318,126 104x104]
PaintableWithLines (BlockContainer<a>) [319,127 102x102]
PaintableWithLines (BlockContainer<DIV>) [8,230 784x42]
Expand Down
28 changes: 14 additions & 14 deletions Tests/LibWeb/Layout/expected/svg-text-dominant-baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,25 @@ Viewport <#document> at [0,0] [0+0+0 800 0+0+0] [0+0+0 600 0+0+0] [BFC] children
TextNode <#text> (not painted)
SVGGeometryBox <line> at [-0.25,349.75] [0+0+0 400.5 0+0+0] [0+0+0 0.5 0+0+0] children: not-inline
TextNode <#text> (not painted)
SVGTextBox <text> at [10.171875,36.90625] [0+0+0 44.4375 0+0+0] [0+0+0 14 0+0+0] children: inline
SVGTextBox <text> at [10.171875,36.90625] [0+0+0 43.90625 0+0+0] [0+0+0 14 0+0+0] children: inline
TextNode <#text> (not painted)
TextNode <#text> (not painted)
SVGTextBox <text> at [10.171875,84.96875] [0+0+0 100.59375 0+0+0] [0+0+0 15.703125 0+0+0] children: inline
SVGTextBox <text> at [10.171875,84.96875] [0+0+0 98.703125 0+0+0] [0+0+0 15.703125 0+0+0] children: inline
TextNode <#text> (not painted)
TextNode <#text> (not painted)
SVGTextBox <text> at [11.015625,142.90625] [0+0+0 68.890625 0+0+0] [0+0+0 14.03125 0+0+0] children: inline
SVGTextBox <text> at [11.015625,142.90625] [0+0+0 68.21875 0+0+0] [0+0+0 14.03125 0+0+0] children: inline
TextNode <#text> (not painted)
TextNode <#text> (not painted)
SVGTextBox <text> at [11.3125,190.328125] [0+0+0 54.46875 0+0+0] [0+0+0 15.46875 0+0+0] children: inline
SVGTextBox <text> at [11.3125,190.328125] [0+0+0 54.375 0+0+0] [0+0+0 15.46875 0+0+0] children: inline
TextNode <#text> (not painted)
TextNode <#text> (not painted)
SVGTextBox <text> at [10.859375,247.75] [0+0+0 70.625 0+0+0] [0+0+0 17.515625 0+0+0] children: inline
SVGTextBox <text> at [10.859375,247.75] [0+0+0 70.125 0+0+0] [0+0+0 17.515625 0+0+0] children: inline
TextNode <#text> (not painted)
TextNode <#text> (not painted)
SVGTextBox <text> at [11.265625,280.953125] [0+0+0 110.5 0+0+0] [0+0+0 17.515625 0+0+0] children: inline
SVGTextBox <text> at [11.265625,280.953125] [0+0+0 109.171875 0+0+0] [0+0+0 17.515625 0+0+0] children: inline
TextNode <#text> (not painted)
TextNode <#text> (not painted)
SVGTextBox <text> at [11.3125,342.953125] [0+0+0 126.59375 0+0+0] [0+0+0 15.46875 0+0+0] children: inline
SVGTextBox <text> at [11.3125,342.953125] [0+0+0 124.65625 0+0+0] [0+0+0 15.46875 0+0+0] children: inline
TextNode <#text> (not painted)
TextNode <#text> (not painted)
TextNode <#text> (not painted)
Expand All @@ -52,13 +52,13 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600]
SVGPathPaintable (SVGGeometryBox<line>) [-0.25,249.75 400.5x0.5]
SVGPathPaintable (SVGGeometryBox<line>) [-0.25,299.75 400.5x0.5]
SVGPathPaintable (SVGGeometryBox<line>) [-0.25,349.75 400.5x0.5]
SVGPathPaintable (SVGTextBox<text>) [10.171875,36.90625 44.4375x14]
SVGPathPaintable (SVGTextBox<text>) [10.171875,84.96875 100.59375x15.703125]
SVGPathPaintable (SVGTextBox<text>) [11.015625,142.90625 68.890625x14.03125]
SVGPathPaintable (SVGTextBox<text>) [11.3125,190.328125 54.46875x15.46875]
SVGPathPaintable (SVGTextBox<text>) [10.859375,247.75 70.625x17.515625]
SVGPathPaintable (SVGTextBox<text>) [11.265625,280.953125 110.5x17.515625]
SVGPathPaintable (SVGTextBox<text>) [11.3125,342.953125 126.59375x15.46875]
SVGPathPaintable (SVGTextBox<text>) [10.171875,36.90625 43.90625x14]
SVGPathPaintable (SVGTextBox<text>) [10.171875,84.96875 98.703125x15.703125]
SVGPathPaintable (SVGTextBox<text>) [11.015625,142.90625 68.21875x14.03125]
SVGPathPaintable (SVGTextBox<text>) [11.3125,190.328125 54.375x15.46875]
SVGPathPaintable (SVGTextBox<text>) [10.859375,247.75 70.125x17.515625]
SVGPathPaintable (SVGTextBox<text>) [11.265625,280.953125 109.171875x17.515625]
SVGPathPaintable (SVGTextBox<text>) [11.3125,342.953125 124.65625x15.46875]

SC for Viewport<#document> [0,0 800x600] (z-index: auto)
SC for BlockContainer<HTML> [0,0 800x416] (z-index: auto)
Loading