LibGfx: Support UTF-16 SVG text placement

This commit is contained in:
Timothy Flynn 2025-08-05 07:11:13 -04:00 committed by Jelle Raaijmakers
commit d893d3234d
Notes: github-actions[bot] 2025-08-05 13:15:00 +00:00
3 changed files with 62 additions and 20 deletions

View file

@ -8,7 +8,6 @@
#include <AK/Forward.h> #include <AK/Forward.h>
#include <AK/NonnullOwnPtr.h> #include <AK/NonnullOwnPtr.h>
#include <AK/Utf8View.h>
#include <LibGfx/AffineTransform.h> #include <LibGfx/AffineTransform.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>
#include <LibGfx/Point.h> #include <LibGfx/Point.h>
@ -32,7 +31,8 @@ public:
virtual void arc_to(FloatPoint point, float radius, bool large_arc, bool sweep) = 0; 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 quadratic_bezier_curve_to(FloatPoint through, FloatPoint point) = 0;
virtual void cubic_bezier_curve_to(FloatPoint c1, FloatPoint c2, FloatPoint p2) = 0; virtual void cubic_bezier_curve_to(FloatPoint c1, FloatPoint c2, FloatPoint p2) = 0;
virtual void text(Utf8View, Font const&) = 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 glyph_run(GlyphRun const&) = 0;
virtual void offset(Gfx::FloatPoint const&) = 0; virtual void offset(Gfx::FloatPoint const&) = 0;
@ -47,7 +47,8 @@ public:
virtual NonnullOwnPtr<PathImpl> clone() const = 0; virtual NonnullOwnPtr<PathImpl> clone() const = 0;
virtual NonnullOwnPtr<PathImpl> copy_transformed(Gfx::AffineTransform const&) const = 0; virtual NonnullOwnPtr<PathImpl> copy_transformed(Gfx::AffineTransform const&) const = 0;
virtual NonnullOwnPtr<PathImpl> place_text_along(Utf8View text, Font const&) const = 0; virtual NonnullOwnPtr<PathImpl> place_text_along(Utf8View const& text, Font const&) const = 0;
virtual NonnullOwnPtr<PathImpl> place_text_along(Utf16View const& text, Font const&) const = 0;
}; };
class Path { class Path {
@ -91,7 +92,8 @@ public:
void arc_to(FloatPoint point, float radius, bool large_arc, bool sweep) { impl().arc_to(point, radius, large_arc, sweep); } 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 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 cubic_bezier_curve_to(FloatPoint c1, FloatPoint c2, FloatPoint p2) { impl().cubic_bezier_curve_to(c1, c2, p2); }
void text(Utf8View text, Font const& font) { impl().text(text, font); } 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 glyph_run(GlyphRun const& glyph_run) { impl().glyph_run(glyph_run); }
void offset(Gfx::FloatPoint const& offset) { impl().offset(offset); } void offset(Gfx::FloatPoint const& offset) { impl().offset(offset); }
@ -109,7 +111,8 @@ public:
Gfx::Path clone() const { return Gfx::Path { impl().clone() }; } 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 copy_transformed(Gfx::AffineTransform const& transform) const { return Gfx::Path { impl().copy_transformed(transform) }; }
Gfx::Path place_text_along(Utf8View text, Font const& font) const { return Gfx::Path { impl().place_text_along(text, font) }; } Gfx::Path place_text_along(Utf8View const& text, Font const& font) const { return Gfx::Path { impl().place_text_along(text, font) }; }
Gfx::Path place_text_along(Utf16View const& text, Font const& font) const { return Gfx::Path { impl().place_text_along(text, font) }; }
void transform(Gfx::AffineTransform const& transform) { m_impl = impl().copy_transformed(transform); } void transform(Gfx::AffineTransform const& transform) { m_impl = impl().copy_transformed(transform); }

View file

@ -7,6 +7,8 @@
#define AK_DONT_REPLACE_STD #define AK_DONT_REPLACE_STD
#include <AK/TypeCasts.h> #include <AK/TypeCasts.h>
#include <AK/Utf16View.h>
#include <AK/Utf8View.h>
#include <LibGfx/Font/Font.h> #include <LibGfx/Font/Font.h>
#include <LibGfx/PathSkia.h> #include <LibGfx/PathSkia.h>
#include <LibGfx/Rect.h> #include <LibGfx/Rect.h>
@ -136,11 +138,21 @@ void PathImplSkia::cubic_bezier_curve_to(FloatPoint c1, FloatPoint c2, FloatPoin
m_path->cubicTo(c1.x(), c1.y(), c2.x(), c2.y(), p2.x(), p2.y()); m_path->cubicTo(c1.x(), c1.y(), c2.x(), c2.y(), p2.x(), p2.y());
} }
void PathImplSkia::text(Utf8View string, Font const& font) void PathImplSkia::text(Utf8View const& string, Font const& font)
{ {
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), m_path.ptr()); 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), m_path.ptr());
} }
void PathImplSkia::text(Utf16View const& string, Font const& font)
{
if (string.has_ascii_storage()) {
text(Utf8View { string.bytes() }, font);
return;
}
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), m_path.ptr());
}
void PathImplSkia::glyph_run(GlyphRun const& glyph_run) void PathImplSkia::glyph_run(GlyphRun const& glyph_run)
{ {
auto sk_font = glyph_run.font().skia_font(1); auto sk_font = glyph_run.font().skia_font(1);
@ -160,37 +172,41 @@ void PathImplSkia::offset(Gfx::FloatPoint const& offset)
m_path->offset(offset.x(), offset.y()); m_path->offset(offset.x(), offset.y());
} }
NonnullOwnPtr<PathImpl> PathImplSkia::place_text_along(Utf8View text, Font const& font) const template<typename TextToGlyphs>
static NonnullOwnPtr<PathImpl> place_text_along_impl(SkPath const& path, Font const& font, size_t length_in_code_points, TextToGlyphs&& text_to_glyphs)
{ {
auto sk_font = font.skia_font(1); auto sk_font = font.skia_font(1);
size_t const text_length = text.length();
SkScalar x = 0; SkScalar x = 0;
SkScalar y = 0; SkScalar y = 0;
SkTextBlobBuilder builder; SkTextBlobBuilder builder;
SkTextBlobBuilder::RunBuffer runBuffer = builder.allocRun(sk_font, text_length, x, y, nullptr); auto const& run_buffer = builder.allocRun(sk_font, static_cast<int>(length_in_code_points), x, y, nullptr);
sk_font.textToGlyphs(text.as_string().characters_without_null_termination(), text.as_string().length(), SkTextEncoding::kUTF8, runBuffer.glyphs, text_length); text_to_glyphs(sk_font, run_buffer);
SkPathMeasure pathMeasure(*m_path, false);
SkPathMeasure path_measure(path, false);
SkScalar accumulated_distance = 0; SkScalar accumulated_distance = 0;
auto output_path = PathImplSkia::create(); auto output_path = PathImplSkia::create();
for (size_t i = 0; i < text_length; ++i) {
SkGlyphID glyph = runBuffer.glyphs[i]; for (size_t i = 0; i < length_in_code_points; ++i) {
SkPath glyphPath; SkGlyphID glyph = run_buffer.glyphs[i];
sk_font.getPath(glyph, &glyphPath); SkPath glyph_path;
sk_font.getPath(glyph, &glyph_path);
SkScalar advance; SkScalar advance;
sk_font.getWidths(&glyph, 1, &advance); sk_font.getWidths(&glyph, 1, &advance);
SkPoint position; SkPoint position;
SkVector tangent; SkVector tangent;
if (!pathMeasure.getPosTan(accumulated_distance, &position, &tangent)) if (!path_measure.getPosTan(accumulated_distance, &position, &tangent))
continue; continue;
SkMatrix matrix; SkMatrix matrix;
matrix.setTranslate(position.x(), position.y()); matrix.setTranslate(position.x(), position.y());
matrix.preRotate(SkRadiansToDegrees(std::atan2(tangent.y(), tangent.x()))); matrix.preRotate(SkRadiansToDegrees(std::atan2(tangent.y(), tangent.x())));
glyphPath.transform(matrix); glyph_path.transform(matrix);
output_path->sk_path().addPath(glyphPath); output_path->sk_path().addPath(glyph_path);
accumulated_distance += advance; accumulated_distance += advance;
} }
@ -198,6 +214,27 @@ NonnullOwnPtr<PathImpl> PathImplSkia::place_text_along(Utf8View text, Font const
return output_path; return output_path;
} }
NonnullOwnPtr<PathImpl> PathImplSkia::place_text_along(Utf8View const& text, Font const& font) const
{
auto length_in_code_points = text.length();
return place_text_along_impl(*m_path, font, length_in_code_points, [&](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) const
{
if (text.has_ascii_storage())
return place_text_along(Utf8View { text.bytes() }, font);
auto length_in_code_points = text.length_in_code_points();
return place_text_along_impl(*m_path, font, length_in_code_points, [&](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) void PathImplSkia::append_path(Gfx::Path const& other)
{ {
m_path->addPath(static_cast<PathImplSkia const&>(other.impl()).sk_path()); m_path->addPath(static_cast<PathImplSkia const&>(other.impl()).sk_path());

View file

@ -27,7 +27,8 @@ public:
virtual void arc_to(FloatPoint point, float radius, bool large_arc, bool sweep) override; 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 quadratic_bezier_curve_to(FloatPoint through, FloatPoint point) override;
virtual void cubic_bezier_curve_to(FloatPoint c1, FloatPoint c2, FloatPoint p2) override; virtual void cubic_bezier_curve_to(FloatPoint c1, FloatPoint c2, FloatPoint p2) override;
virtual void text(Utf8View, Font const&) 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 glyph_run(GlyphRun const&) override;
virtual void offset(Gfx::FloatPoint const&) override; virtual void offset(Gfx::FloatPoint const&) override;
@ -42,7 +43,8 @@ public:
virtual NonnullOwnPtr<PathImpl> clone() const override; virtual NonnullOwnPtr<PathImpl> clone() const override;
virtual NonnullOwnPtr<PathImpl> copy_transformed(Gfx::AffineTransform const&) const override; virtual NonnullOwnPtr<PathImpl> copy_transformed(Gfx::AffineTransform const&) const override;
virtual NonnullOwnPtr<PathImpl> place_text_along(Utf8View text, Font const&) const override; virtual NonnullOwnPtr<PathImpl> place_text_along(Utf8View const& text, Font const&) const override;
virtual NonnullOwnPtr<PathImpl> place_text_along(Utf16View const& text, Font const&) const override;
SkPath const& sk_path() const { return *m_path; } SkPath const& sk_path() const { return *m_path; }
SkPath& sk_path() { return *m_path; } SkPath& sk_path() { return *m_path; }