mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 01:17:34 +00:00
LibGfx: Reduce code duplication in Painter::draw_text
Use the same logic for all variants for Painter::draw_text. Also, add an overload that allows taking a callback function for custom gylph drawing. This allows drawing some glyphs differently in the correct location when drawing more complex strings (e.g. multi-line, elisions, etc).
This commit is contained in:
parent
25e7225782
commit
f239fbaa58
2 changed files with 132 additions and 172 deletions
|
@ -854,20 +854,52 @@ void Painter::draw_glyph_or_emoji(const IntPoint& point, u32 code_point, const F
|
||||||
draw_emoji(point, *emoji, font);
|
draw_emoji(point, *emoji, font);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Painter::draw_text_line(const IntRect& a_rect, const Utf8View& text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
|
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, DrawGlyphFunction draw_glyph)
|
||||||
{
|
{
|
||||||
auto rect = a_rect;
|
auto rect = a_rect;
|
||||||
Utf8View final_text(text);
|
TextType final_text(text);
|
||||||
String elided_text;
|
typename ElidedText<TextType>::Type elided_text;
|
||||||
if (elision == TextElision::Right) {
|
if (elision == TextElision::Right) {
|
||||||
int text_width = font.width(final_text);
|
int text_width = font.width(final_text);
|
||||||
if (font.width(final_text) > rect.width()) {
|
if (font.width(final_text) > rect.width()) {
|
||||||
int glyph_spacing = font.glyph_spacing();
|
int glyph_spacing = font.glyph_spacing();
|
||||||
int byte_offset = 0;
|
|
||||||
int new_width = font.width("...");
|
int new_width = font.width("...");
|
||||||
if (new_width < text_width) {
|
if (new_width < text_width) {
|
||||||
for (auto it = final_text.begin(); it != final_text.end(); ++it) {
|
size_t offset = 0;
|
||||||
u32 code_point = *it;
|
for (auto code_point : text) {
|
||||||
int glyph_width = font.glyph_or_emoji_width(code_point);
|
int glyph_width = font.glyph_or_emoji_width(code_point);
|
||||||
// NOTE: Glyph spacing should not be added after the last glyph on the line,
|
// 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,
|
// but since we are here because the last glyph does not actually fit on the line,
|
||||||
|
@ -875,14 +907,10 @@ void Painter::draw_text_line(const IntRect& a_rect, const Utf8View& text, const
|
||||||
int width_with_this_glyph_included = new_width + glyph_width + glyph_spacing;
|
int width_with_this_glyph_included = new_width + glyph_width + glyph_spacing;
|
||||||
if (width_with_this_glyph_included > rect.width())
|
if (width_with_this_glyph_included > rect.width())
|
||||||
break;
|
break;
|
||||||
byte_offset = final_text.byte_offset_of(it);
|
|
||||||
new_width += glyph_width + glyph_spacing;
|
new_width += glyph_width + glyph_spacing;
|
||||||
|
offset++;
|
||||||
}
|
}
|
||||||
StringBuilder builder;
|
apply_elision(final_text, elided_text, offset);
|
||||||
builder.append(final_text.substring_view(0, byte_offset).as_string());
|
|
||||||
builder.append("...");
|
|
||||||
elided_text = builder.to_string();
|
|
||||||
final_text = Utf8View { elided_text };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -920,74 +948,88 @@ void Painter::draw_text_line(const IntRect& a_rect, const Utf8View& text, const
|
||||||
point.move_by(space_width, 0);
|
point.move_by(space_width, 0);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
draw_glyph_or_emoji(point, code_point, font, color);
|
IntSize glyph_size(font.glyph_or_emoji_width(code_point) + font.glyph_spacing(), font.glyph_height());
|
||||||
point.move_by(font.glyph_or_emoji_width(code_point) + font.glyph_spacing(), 0);
|
draw_glyph({ point, glyph_size }, code_point);
|
||||||
|
point.move_by(glyph_size.width(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Painter::draw_text_line(const IntRect& a_rect, const Utf32View& text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
|
static inline size_t draw_text_iterator_offset(const Utf8View& text, const Utf8View::Iterator& it)
|
||||||
{
|
{
|
||||||
auto rect = a_rect;
|
return text.byte_offset_of(it);
|
||||||
Utf32View final_text(text);
|
}
|
||||||
Vector<u32> elided_text;
|
|
||||||
if (elision == TextElision::Right) {
|
static inline size_t draw_text_iterator_offset(const Utf32View& text, const Utf32View::Iterator& it)
|
||||||
int text_width = font.width(final_text);
|
{
|
||||||
if (font.width(final_text) > rect.width()) {
|
return it - text.begin();
|
||||||
int glyph_spacing = font.glyph_spacing();
|
}
|
||||||
int new_width = font.width("...");
|
|
||||||
if (new_width < text_width) {
|
static inline size_t draw_text_get_length(const Utf8View& text)
|
||||||
size_t i = 0;
|
{
|
||||||
for (; i < text.length(); ++i) {
|
return text.byte_length();
|
||||||
u32 code_point = text.code_points()[i];
|
}
|
||||||
int glyph_width = font.glyph_or_emoji_width(code_point);
|
|
||||||
// NOTE: Glyph spacing should not be added after the last glyph on the line,
|
static inline size_t draw_text_get_length(const Utf32View& text)
|
||||||
// but since we are here because the last glyph does not actually fit on the line,
|
{
|
||||||
// we don't have to worry about spacing.
|
return text.length();
|
||||||
int width_with_this_glyph_included = new_width + glyph_width + glyph_spacing;
|
}
|
||||||
if (width_with_this_glyph_included > rect.width())
|
|
||||||
break;
|
template<typename TextType, typename DrawGlyphFunction>
|
||||||
new_width += glyph_width + glyph_spacing;
|
void do_draw_text(const IntRect& rect, const TextType& text, const Font& font, TextAlignment alignment, TextElision elision, DrawGlyphFunction draw_glyph)
|
||||||
}
|
{
|
||||||
elided_text.clear();
|
Vector<TextType, 32> lines;
|
||||||
elided_text.append(final_text.code_points(), i);
|
|
||||||
elided_text.append('.');
|
size_t start_of_current_line = 0;
|
||||||
elided_text.append('.');
|
for (auto it = text.begin(); it != text.end(); ++it) {
|
||||||
elided_text.append('.');
|
u32 code_point = *it;
|
||||||
final_text = Utf32View { elided_text.data(), elided_text.size() };
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
switch (alignment) {
|
switch (alignment) {
|
||||||
case TextAlignment::TopLeft:
|
case TextAlignment::TopLeft:
|
||||||
case TextAlignment::CenterLeft:
|
bounding_rect.set_location(rect.location());
|
||||||
break;
|
break;
|
||||||
case TextAlignment::TopRight:
|
case TextAlignment::TopRight:
|
||||||
|
bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.y() });
|
||||||
|
break;
|
||||||
|
case TextAlignment::CenterLeft:
|
||||||
|
bounding_rect.set_location({ rect.x(), rect.center().y() - (bounding_rect.height() / 2) });
|
||||||
|
break;
|
||||||
case TextAlignment::CenterRight:
|
case TextAlignment::CenterRight:
|
||||||
rect.set_x(rect.right() - font.width(final_text));
|
bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.center().y() - (bounding_rect.height() / 2) });
|
||||||
break;
|
break;
|
||||||
case TextAlignment::Center: {
|
case TextAlignment::Center:
|
||||||
auto shrunken_rect = rect;
|
bounding_rect.center_within(rect);
|
||||||
shrunken_rect.set_width(font.width(final_text));
|
|
||||||
shrunken_rect.center_within(rect);
|
|
||||||
rect = shrunken_rect;
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
ASSERT_NOT_REACHED();
|
ASSERT_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto point = rect.location();
|
for (size_t i = 0; i < lines.size(); ++i) {
|
||||||
int space_width = font.glyph_width(' ') + font.glyph_spacing();
|
auto& line = lines[i];
|
||||||
|
IntRect line_rect { bounding_rect.x(), bounding_rect.y() + static_cast<int>(i) * line_height, bounding_rect.width(), line_height };
|
||||||
for (size_t i = 0; i < final_text.length(); ++i) {
|
line_rect.intersect(rect);
|
||||||
auto code_point = final_text.code_points()[i];
|
draw_text_line(line_rect, line, font, alignment, elision, draw_glyph);
|
||||||
if (code_point == ' ') {
|
|
||||||
point.move_by(space_width, 0);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
draw_glyph_or_emoji(point, code_point, font, color);
|
|
||||||
point.move_by(font.glyph_or_emoji_width(code_point) + font.glyph_spacing(), 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1004,120 +1046,38 @@ void Painter::draw_text(const IntRect& rect, const Utf32View& text, TextAlignmen
|
||||||
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)
|
||||||
{
|
{
|
||||||
Utf8View text { raw_text };
|
Utf8View text { raw_text };
|
||||||
Vector<Utf8View, 32> lines;
|
do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
|
||||||
|
draw_glyph_or_emoji(r.location(), code_point, font, color);
|
||||||
int start_of_current_line = 0;
|
});
|
||||||
for (auto it = text.begin(); it != text.end(); ++it) {
|
|
||||||
u32 code_point = *it;
|
|
||||||
if (code_point == '\n') {
|
|
||||||
int byte_offset = text.byte_offset_of(it);
|
|
||||||
Utf8View line = text.substring_view(start_of_current_line, byte_offset - start_of_current_line);
|
|
||||||
lines.append(line);
|
|
||||||
start_of_current_line = byte_offset + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start_of_current_line != text.byte_length()) {
|
|
||||||
Utf8View line = text.substring_view(start_of_current_line, text.byte_length() - start_of_current_line);
|
|
||||||
lines.append(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (alignment) {
|
|
||||||
case TextAlignment::TopLeft:
|
|
||||||
bounding_rect.set_location(rect.location());
|
|
||||||
break;
|
|
||||||
case TextAlignment::TopRight:
|
|
||||||
bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.y() });
|
|
||||||
break;
|
|
||||||
case TextAlignment::CenterLeft:
|
|
||||||
bounding_rect.set_location({ rect.x(), rect.center().y() - (bounding_rect.height() / 2) });
|
|
||||||
break;
|
|
||||||
case TextAlignment::CenterRight:
|
|
||||||
bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.center().y() - (bounding_rect.height() / 2) });
|
|
||||||
break;
|
|
||||||
case TextAlignment::Center:
|
|
||||||
bounding_rect.center_within(rect);
|
|
||||||
break;
|
|
||||||
case TextAlignment::BottomRight:
|
|
||||||
bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), (rect.bottom() + 1) - bounding_rect.height() });
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ASSERT_NOT_REACHED();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < lines.size(); ++i) {
|
|
||||||
auto& line = lines[i];
|
|
||||||
IntRect line_rect { bounding_rect.x(), bounding_rect.y() + static_cast<int>(i) * line_height, bounding_rect.width(), line_height };
|
|
||||||
line_rect.intersect(rect);
|
|
||||||
draw_text_line(line_rect, line, font, alignment, color, elision);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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& text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
|
||||||
{
|
{
|
||||||
Vector<Utf32View, 32> lines;
|
do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
|
||||||
|
draw_glyph_or_emoji(r.location(), code_point, font, color);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
size_t start_of_current_line = 0;
|
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)
|
||||||
for (size_t i = 0; i < text.length(); ++i) {
|
{
|
||||||
u32 code_point = text.code_points()[i];
|
Utf8View text { raw_text };
|
||||||
if (code_point == '\n') {
|
do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
|
||||||
Utf32View line = text.substring_view(start_of_current_line, i - start_of_current_line);
|
draw_one_glyph(r, code_point);
|
||||||
lines.append(line);
|
});
|
||||||
start_of_current_line = i + 1;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start_of_current_line != text.length()) {
|
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)
|
||||||
Utf32View line = text.substring_view(start_of_current_line, text.length() - start_of_current_line);
|
{
|
||||||
lines.append(line);
|
do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
|
||||||
}
|
draw_one_glyph(r, code_point);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static const int line_spacing = 4;
|
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)
|
||||||
int line_height = font.glyph_height() + line_spacing;
|
{
|
||||||
IntRect bounding_rect { 0, 0, 0, (static_cast<int>(lines.size()) * line_height) - line_spacing };
|
do_draw_text(rect, text, font, alignment, elision, [&](const IntRect& r, u32 code_point) {
|
||||||
|
draw_one_glyph(r, code_point);
|
||||||
for (auto& line : lines) {
|
});
|
||||||
auto line_width = font.width(line);
|
|
||||||
if (line_width > bounding_rect.width())
|
|
||||||
bounding_rect.set_width(line_width);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (alignment) {
|
|
||||||
case TextAlignment::TopLeft:
|
|
||||||
bounding_rect.set_location(rect.location());
|
|
||||||
break;
|
|
||||||
case TextAlignment::TopRight:
|
|
||||||
bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.y() });
|
|
||||||
break;
|
|
||||||
case TextAlignment::CenterLeft:
|
|
||||||
bounding_rect.set_location({ rect.x(), rect.center().y() - (bounding_rect.height() / 2) });
|
|
||||||
break;
|
|
||||||
case TextAlignment::CenterRight:
|
|
||||||
bounding_rect.set_location({ (rect.right() + 1) - bounding_rect.width(), rect.center().y() - (bounding_rect.height() / 2) });
|
|
||||||
break;
|
|
||||||
case TextAlignment::Center:
|
|
||||||
bounding_rect.center_within(rect);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ASSERT_NOT_REACHED();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < lines.size(); ++i) {
|
|
||||||
auto& line = lines[i];
|
|
||||||
IntRect line_rect { bounding_rect.x(), bounding_rect.y() + static_cast<int>(i) * line_height, bounding_rect.width(), line_height };
|
|
||||||
line_rect.intersect(rect);
|
|
||||||
draw_text_line(line_rect, line, font, alignment, color, elision);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Painter::set_pixel(const IntPoint& p, Color color)
|
void Painter::set_pixel(const IntPoint& p, Color color)
|
||||||
|
|
|
@ -79,6 +79,9 @@ public:
|
||||||
void draw_text(const IntRect&, const StringView&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
|
void draw_text(const IntRect&, const StringView&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
|
||||||
void draw_text(const IntRect&, const Utf32View&, const Font&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
|
void draw_text(const IntRect&, const Utf32View&, const Font&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
|
||||||
void draw_text(const IntRect&, const Utf32View&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
|
void draw_text(const IntRect&, const Utf32View&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
|
||||||
|
void draw_text(Function<void(const IntRect&, u32)>, const IntRect&, const StringView&, const Font&, TextAlignment = TextAlignment::TopLeft, TextElision = TextElision::None);
|
||||||
|
void draw_text(Function<void(const IntRect&, u32)>, const IntRect&, const Utf8View&, const Font&, TextAlignment = TextAlignment::TopLeft, TextElision = TextElision::None);
|
||||||
|
void draw_text(Function<void(const IntRect&, u32)>, const IntRect&, const Utf32View&, const Font&, TextAlignment = TextAlignment::TopLeft, TextElision = TextElision::None);
|
||||||
void draw_glyph(const IntPoint&, u32, Color);
|
void draw_glyph(const IntPoint&, u32, Color);
|
||||||
void draw_glyph(const IntPoint&, u32, const Font&, Color);
|
void draw_glyph(const IntPoint&, u32, const Font&, Color);
|
||||||
void draw_emoji(const IntPoint&, const Gfx::Bitmap&, const Font&);
|
void draw_emoji(const IntPoint&, const Gfx::Bitmap&, const Font&);
|
||||||
|
@ -135,9 +138,6 @@ protected:
|
||||||
void blit_with_opacity(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, float opacity);
|
void blit_with_opacity(const IntPoint&, const Gfx::Bitmap&, const IntRect& src_rect, float opacity);
|
||||||
void draw_pixel(const IntPoint&, Color, int thickness = 1);
|
void draw_pixel(const IntPoint&, Color, int thickness = 1);
|
||||||
|
|
||||||
void draw_text_line(const IntRect&, const Utf8View&, const Font&, TextAlignment, Color, TextElision);
|
|
||||||
void draw_text_line(const IntRect&, const Utf32View&, const Font&, TextAlignment, Color, TextElision);
|
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
const Font* font;
|
const Font* font;
|
||||||
IntPoint translation;
|
IntPoint translation;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue