mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 21:47:46 +00:00
Userland: Move text wrapping/elision into the new TextLayout :^)
This class now contains all the fun bits about laying out text in a rect. It will handle line wrapping at a certain width, cutting off lines that don't fit the given rect, and handling text elision. Painter::draw_text now internally uses this. Future work here would be not laying out text twice (once actually preparing the lines to be rendered and once to get the bounding box), and possibly adding left elision if necessary. Additionally, this commit makes the Utf32View versions of Painter::draw_text convert to Utf8View internally. The intention is to completely remove those versions, but they're kept at the moment to keep the scope of this PR small.
This commit is contained in:
parent
a5a32fbcce
commit
e11940fd01
14 changed files with 371 additions and 238 deletions
|
@ -26,6 +26,7 @@
|
|||
#include <LibGfx/Palette.h>
|
||||
#include <LibGfx/Path.h>
|
||||
#include <LibGfx/TextDirection.h>
|
||||
#include <LibGfx/TextLayout.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
|
@ -1204,67 +1205,10 @@ void Painter::draw_glyph_or_emoji(const IntPoint& point, u32 code_point, const F
|
|||
draw_emoji(point, *emoji, font);
|
||||
}
|
||||
|
||||
static void apply_elision(Utf8View& final_text, String& elided_text, size_t offset)
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append(final_text.substring_view(0, offset).as_string());
|
||||
builder.append("...");
|
||||
elided_text = builder.to_string();
|
||||
final_text = Utf8View { elided_text };
|
||||
}
|
||||
|
||||
static void apply_elision(Utf32View& final_text, Vector<u32>& elided_text, size_t offset)
|
||||
{
|
||||
elided_text.append(final_text.code_points(), offset);
|
||||
elided_text.append('.');
|
||||
elided_text.append('.');
|
||||
elided_text.append('.');
|
||||
final_text = Utf32View { elided_text.data(), elided_text.size() };
|
||||
}
|
||||
|
||||
template<typename TextType>
|
||||
struct ElidedText {
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ElidedText<Utf8View> {
|
||||
typedef String Type;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ElidedText<Utf32View> {
|
||||
typedef Vector<u32> Type;
|
||||
};
|
||||
|
||||
template<typename TextType, typename DrawGlyphFunction>
|
||||
void draw_text_line(const IntRect& a_rect, const TextType& text, const Font& font, TextAlignment alignment, TextElision elision, TextDirection direction, DrawGlyphFunction draw_glyph)
|
||||
template<typename DrawGlyphFunction>
|
||||
void draw_text_line(IntRect const& a_rect, Utf8View const& text, Font const& font, TextAlignment alignment, TextDirection direction, DrawGlyphFunction draw_glyph)
|
||||
{
|
||||
auto rect = a_rect;
|
||||
TextType final_text(text);
|
||||
typename ElidedText<TextType>::Type elided_text;
|
||||
if (elision == TextElision::Right) { // FIXME: This needs to be specialized for bidirectional text
|
||||
int text_width = font.width(final_text);
|
||||
if (font.width(final_text) > rect.width()) {
|
||||
int glyph_spacing = font.glyph_spacing();
|
||||
int new_width = font.width("...");
|
||||
if (new_width < text_width) {
|
||||
size_t offset = 0;
|
||||
for (auto it = text.begin(); it != text.end(); ++it) {
|
||||
auto code_point = *it;
|
||||
int glyph_width = font.glyph_or_emoji_width(code_point);
|
||||
// NOTE: Glyph spacing should not be added after the last glyph on the line,
|
||||
// but since we are here because the last glyph does not actually fit on the line,
|
||||
// we don't have to worry about spacing.
|
||||
int width_with_this_glyph_included = new_width + glyph_width + glyph_spacing;
|
||||
if (width_with_this_glyph_included > rect.width())
|
||||
break;
|
||||
new_width += glyph_width + glyph_spacing;
|
||||
offset = text.iterator_offset(it);
|
||||
}
|
||||
apply_elision(final_text, elided_text, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (alignment) {
|
||||
case TextAlignment::TopLeft:
|
||||
|
@ -1274,11 +1218,11 @@ void draw_text_line(const IntRect& a_rect, const TextType& text, const Font& fon
|
|||
case TextAlignment::TopRight:
|
||||
case TextAlignment::CenterRight:
|
||||
case TextAlignment::BottomRight:
|
||||
rect.set_x(rect.right() - font.width(final_text));
|
||||
rect.set_x(rect.right() - font.width(text));
|
||||
break;
|
||||
case TextAlignment::Center: {
|
||||
auto shrunken_rect = rect;
|
||||
shrunken_rect.set_width(font.width(final_text));
|
||||
shrunken_rect.set_width(font.width(text));
|
||||
shrunken_rect.center_within(rect);
|
||||
rect = shrunken_rect;
|
||||
break;
|
||||
|
@ -1300,7 +1244,7 @@ void draw_text_line(const IntRect& a_rect, const TextType& text, const Font& fon
|
|||
space_width = -space_width; // Draw spaces backwards
|
||||
}
|
||||
|
||||
for (u32 code_point : final_text) {
|
||||
for (u32 code_point : text) {
|
||||
if (code_point == ' ') {
|
||||
point.translate_by(space_width, 0);
|
||||
continue;
|
||||
|
@ -1314,28 +1258,12 @@ void draw_text_line(const IntRect& a_rect, const TextType& text, const Font& fon
|
|||
}
|
||||
}
|
||||
|
||||
static inline size_t draw_text_iterator_offset(const Utf8View& text, const Utf8View::Iterator& it)
|
||||
{
|
||||
return text.byte_offset_of(it);
|
||||
}
|
||||
|
||||
static inline size_t draw_text_iterator_offset(const Utf32View& text, const Utf32View::Iterator& it)
|
||||
{
|
||||
return it - text.begin();
|
||||
}
|
||||
|
||||
static inline size_t draw_text_get_length(const Utf8View& text)
|
||||
{
|
||||
return text.byte_length();
|
||||
}
|
||||
|
||||
static inline size_t draw_text_get_length(const Utf32View& text)
|
||||
{
|
||||
return text.length();
|
||||
}
|
||||
|
||||
template<typename TextType>
|
||||
Vector<DirectionalRun> split_text_into_directional_runs(const TextType& text, TextDirection initial_direction)
|
||||
Vector<DirectionalRun> Painter::split_text_into_directional_runs(Utf8View const& text, TextDirection initial_direction)
|
||||
{
|
||||
// FIXME: This is a *very* simplified version of the UNICODE BIDIRECTIONAL ALGORITHM (https://www.unicode.org/reports/tr9/), that can render most bidirectional text
|
||||
// but also produces awkward results in a large amount of edge cases. This should probably be replaced with a fully spec compliant implementation at some point.
|
||||
|
@ -1479,8 +1407,7 @@ Vector<DirectionalRun> split_text_into_directional_runs(const TextType& text, Te
|
|||
return runs;
|
||||
}
|
||||
|
||||
template<typename TextType>
|
||||
bool text_contains_bidirectional_text(const TextType& text, TextDirection initial_direction)
|
||||
bool Painter::text_contains_bidirectional_text(Utf8View const& text, TextDirection initial_direction)
|
||||
{
|
||||
for (u32 code_point : text) {
|
||||
auto char_class = get_char_bidi_class(code_point);
|
||||
|
@ -1492,39 +1419,19 @@ bool text_contains_bidirectional_text(const TextType& text, TextDirection initia
|
|||
return false;
|
||||
}
|
||||
|
||||
template<typename TextType, typename DrawGlyphFunction>
|
||||
void do_draw_text(const IntRect& rect, const TextType& text, const Font& font, TextAlignment alignment, TextElision elision, DrawGlyphFunction draw_glyph)
|
||||
template<typename DrawGlyphFunction>
|
||||
void Painter::do_draw_text(IntRect const& rect, Utf8View const& text, Font const& font, TextAlignment alignment, TextElision elision, TextWrapping wrapping, DrawGlyphFunction draw_glyph)
|
||||
{
|
||||
if (draw_text_get_length(text) == 0)
|
||||
return;
|
||||
|
||||
Vector<TextType, 32> lines;
|
||||
|
||||
size_t start_of_current_line = 0;
|
||||
for (auto it = text.begin(); it != text.end(); ++it) {
|
||||
u32 code_point = *it;
|
||||
if (code_point == '\n') {
|
||||
auto offset = draw_text_iterator_offset(text, it);
|
||||
TextType line = text.substring_view(start_of_current_line, offset - start_of_current_line);
|
||||
lines.append(line);
|
||||
start_of_current_line = offset + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (start_of_current_line != draw_text_get_length(text)) {
|
||||
TextType line = text.substring_view(start_of_current_line, draw_text_get_length(text) - start_of_current_line);
|
||||
lines.append(line);
|
||||
}
|
||||
TextLayout layout(&font, text, rect);
|
||||
|
||||
static const int line_spacing = 4;
|
||||
int line_height = font.glyph_height() + line_spacing;
|
||||
IntRect bounding_rect { 0, 0, 0, (static_cast<int>(lines.size()) * line_height) - line_spacing };
|
||||
|
||||
for (auto& line : lines) {
|
||||
auto line_width = font.width(line);
|
||||
if (line_width > bounding_rect.width())
|
||||
bounding_rect.set_width(line_width);
|
||||
}
|
||||
auto lines = layout.lines(elision, wrapping, line_spacing);
|
||||
auto bounding_rect = layout.bounding_rect(wrapping, line_spacing);
|
||||
|
||||
switch (alignment) {
|
||||
case TextAlignment::TopLeft:
|
||||
|
@ -1559,8 +1466,8 @@ void do_draw_text(const IntRect& rect, const TextType& text, const Font& font, T
|
|||
line_rect.intersect(rect);
|
||||
|
||||
TextDirection line_direction = get_text_direction(line);
|
||||
if (text_contains_bidirectional_text(line, line_direction)) { // Slow Path: The line contains mixed BiDi classes
|
||||
auto directional_runs = split_text_into_directional_runs(line, line_direction);
|
||||
if (text_contains_bidirectional_text(Utf8View { line }, line_direction)) { // Slow Path: The line contains mixed BiDi classes
|
||||
auto directional_runs = split_text_into_directional_runs(Utf8View { line }, line_direction);
|
||||
auto current_dx = line_direction == TextDirection::LTR ? 0 : line_rect.width();
|
||||
for (auto& directional_run : directional_runs) {
|
||||
auto run_width = font.width(directional_run.text());
|
||||
|
@ -1568,65 +1475,82 @@ void do_draw_text(const IntRect& rect, const TextType& text, const Font& font, T
|
|||
current_dx -= run_width;
|
||||
auto run_rect = line_rect.translated(current_dx, 0);
|
||||
run_rect.set_width(run_width);
|
||||
draw_text_line(run_rect, directional_run.text(), font, alignment, elision, directional_run.direction(), draw_glyph);
|
||||
|
||||
// NOTE: DirectionalRun returns Utf32View which isn't
|
||||
// compatible with draw_text_line.
|
||||
StringBuilder builder;
|
||||
builder.append(directional_run.text());
|
||||
auto text = Utf8View { builder.to_string() };
|
||||
|
||||
draw_text_line(run_rect, text, font, alignment, directional_run.direction(), draw_glyph);
|
||||
if (line_direction == TextDirection::LTR)
|
||||
current_dx += run_width;
|
||||
}
|
||||
} else {
|
||||
draw_text_line(line_rect, line, font, alignment, elision, line_direction, draw_glyph);
|
||||
draw_text_line(line_rect, Utf8View { line }, font, alignment, line_direction, draw_glyph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::draw_text(const IntRect& rect, const StringView& text, TextAlignment alignment, Color color, TextElision elision)
|
||||
void Painter::draw_text(const IntRect& rect, const StringView& text, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
|
||||
{
|
||||
draw_text(rect, text, font(), alignment, color, elision);
|
||||
draw_text(rect, text, font(), alignment, color, elision, wrapping);
|
||||
}
|
||||
|
||||
void Painter::draw_text(const IntRect& rect, const Utf32View& text, TextAlignment alignment, Color color, TextElision elision)
|
||||
void Painter::draw_text(const IntRect& rect, const Utf32View& text, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
|
||||
{
|
||||
draw_text(rect, text, font(), alignment, color, elision);
|
||||
draw_text(rect, text, font(), alignment, color, elision, wrapping);
|
||||
}
|
||||
|
||||
void Painter::draw_text(const IntRect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
|
||||
void Painter::draw_text(const IntRect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
|
||||
{
|
||||
Utf8View text { raw_text };
|
||||
do_draw_text(rect, Utf8View(text), font, alignment, elision, [&](const IntRect& r, u32 code_point) {
|
||||
do_draw_text(rect, text, font, alignment, elision, wrapping, [&](const IntRect& r, u32 code_point) {
|
||||
draw_glyph_or_emoji(r.location(), code_point, font, color);
|
||||
});
|
||||
}
|
||||
|
||||
void Painter::draw_text(const IntRect& rect, const Utf32View& text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
|
||||
void Painter::draw_text(const IntRect& rect, const Utf32View& raw_text, const Font& font, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
|
||||
{
|
||||
do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
|
||||
// FIXME: UTF-32 should eventually be completely removed, but for the time
|
||||
// being some places might depend on it, so we do some internal conversion.
|
||||
StringBuilder builder;
|
||||
builder.append(raw_text);
|
||||
auto text = Utf8View { builder.string_view() };
|
||||
do_draw_text(rect, text, font, alignment, elision, wrapping, [&](const IntRect& r, u32 code_point) {
|
||||
draw_glyph_or_emoji(r.location(), code_point, font, color);
|
||||
});
|
||||
}
|
||||
|
||||
void Painter::draw_text(Function<void(const IntRect&, u32)> draw_one_glyph, const IntRect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, TextElision elision)
|
||||
void Painter::draw_text(Function<void(const IntRect&, u32)> draw_one_glyph, const IntRect& rect, const Utf8View& text, const Font& font, TextAlignment alignment, TextElision elision, TextWrapping wrapping)
|
||||
{
|
||||
VERIFY(scale() == 1); // FIXME: Add scaling support.
|
||||
|
||||
do_draw_text(rect, text, font, alignment, elision, wrapping, [&](const IntRect& r, u32 code_point) {
|
||||
draw_one_glyph(r, code_point);
|
||||
});
|
||||
}
|
||||
|
||||
void Painter::draw_text(Function<void(const IntRect&, u32)> draw_one_glyph, const IntRect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, TextElision elision, TextWrapping wrapping)
|
||||
{
|
||||
VERIFY(scale() == 1); // FIXME: Add scaling support.
|
||||
|
||||
Utf8View text { raw_text };
|
||||
do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
|
||||
do_draw_text(rect, text, font, alignment, elision, wrapping, [&](const IntRect& r, u32 code_point) {
|
||||
draw_one_glyph(r, code_point);
|
||||
});
|
||||
}
|
||||
|
||||
void Painter::draw_text(Function<void(const IntRect&, u32)> draw_one_glyph, const IntRect& rect, const Utf8View& text, const Font& font, TextAlignment alignment, TextElision elision)
|
||||
void Painter::draw_text(Function<void(const IntRect&, u32)> draw_one_glyph, const IntRect& rect, const Utf32View& raw_text, const Font& font, TextAlignment alignment, TextElision elision, TextWrapping wrapping)
|
||||
{
|
||||
VERIFY(scale() == 1); // FIXME: Add scaling support.
|
||||
|
||||
do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
|
||||
draw_one_glyph(r, code_point);
|
||||
});
|
||||
}
|
||||
|
||||
void Painter::draw_text(Function<void(const IntRect&, u32)> draw_one_glyph, const IntRect& rect, const Utf32View& text, const Font& font, TextAlignment alignment, TextElision elision)
|
||||
{
|
||||
VERIFY(scale() == 1); // FIXME: Add scaling support.
|
||||
|
||||
do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
|
||||
// FIXME: UTF-32 should eventually be completely removed, but for the time
|
||||
// being some places might depend on it, so we do some internal conversion.
|
||||
StringBuilder builder;
|
||||
builder.append(raw_text);
|
||||
auto text = Utf8View { builder.string_view() };
|
||||
do_draw_text(rect, text, font, alignment, elision, wrapping, [&](const IntRect& r, u32 code_point) {
|
||||
draw_one_glyph(r, code_point);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue