1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 05:54:58 +00:00

LibGfx: Add Path::place_text_along(text, font)

This method returns a new path with the input text placed along the edge
of the original path.
This commit is contained in:
MacDue 2023-12-18 01:03:45 +00:00 committed by Andreas Kling
parent 8713968165
commit d327104910
2 changed files with 65 additions and 1 deletions

View file

@ -184,6 +184,69 @@ void Path::text(Utf8View text, Font const& font)
IncludeLeftBearing::Yes);
}
Path Path::place_text_along(Utf8View text, Font const& font) const
{
if (!is<ScaledFont>(font)) {
// FIXME: This API only accepts Gfx::Font for ease of use.
dbgln("Cannot path-ify bitmap fonts!");
return {};
}
auto& lines = split_lines();
auto next_point_for_offset = [&, line_index = 0U, distance_along_path = 0.0f, last_line_length = 0.0f](float offset) mutable -> Optional<FloatPoint> {
while (line_index < lines.size() && offset > distance_along_path) {
last_line_length = lines[line_index++].length();
distance_along_path += last_line_length;
}
if (offset > distance_along_path)
return {};
if (last_line_length > 1) {
// If the last line segment was fairly long, compute the point in the line.
float p = (last_line_length + offset - distance_along_path) / last_line_length;
auto current_line = lines[line_index - 1];
return current_line.a() + (current_line.b() - current_line.a()).scaled(p);
}
if (line_index >= lines.size())
return {};
return lines[line_index].a();
};
auto font_list = Gfx::FontCascadeList::create();
font_list->add(font);
auto& scaled_font = static_cast<Gfx::ScaledFont const&>(font);
Gfx::Path result_path;
Gfx::for_each_glyph_position(
{}, text, font_list, [&](Gfx::DrawGlyphOrEmoji glyph_or_emoji) {
auto* glyph = glyph_or_emoji.get_pointer<Gfx::DrawGlyph>();
if (!glyph)
return;
auto offset = glyph->position.x();
auto width = font.glyph_width(glyph->code_point);
auto start = next_point_for_offset(offset);
if (!start.has_value())
return;
auto end = next_point_for_offset(offset + width);
if (!end.has_value())
return;
// Find the angle between the start and end points on the path.
auto delta = *end - *start;
auto angle = AK::atan2(delta.y(), delta.x());
Gfx::Path glyph_path;
// Rotate the glyph then move it to start point.
auto glyph_id = scaled_font.glyph_id_for_code_point(glyph->code_point);
scaled_font.append_glyph_path_to(glyph_path, glyph_id);
auto transform = Gfx::AffineTransform {}
.translate(*start)
.multiply(Gfx::AffineTransform {}.rotate_radians(angle))
.multiply(Gfx::AffineTransform {}.translate({ 0, -scaled_font.pixel_metrics().ascent }));
glyph_path = glyph_path.copy_transformed(transform);
result_path.append_path(glyph_path);
},
Gfx::IncludeLeftBearing::Yes);
return result_path;
}
FloatPoint Path::last_point()
{
FloatPoint last_point { 0, 0 };
@ -400,7 +463,6 @@ void Path::ensure_subpath(FloatPoint point)
m_need_new_subpath = false;
}
}
template<typename T>
struct RoundTrip {
RoundTrip(ReadonlySpan<T> span)

View file

@ -202,6 +202,8 @@ public:
Path stroke_to_fill(float thickness) const;
Path place_text_along(Utf8View text, Font const&) const;
private:
void approximate_elliptical_arc_with_cubic_beziers(FloatPoint center, FloatSize radii, float x_axis_rotation, float theta, float theta_delta);