mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 04:27:44 +00:00
LibDraw: Add emoji drawing support to Painter
From here on, all strings displayed to the user are expected to be encoded as UTF-8. The next few commits will deal with a few existing places where this requirement is currently violated. https://github.com/SerenityOS/serenity/issues/490
This commit is contained in:
parent
27380b9d2b
commit
84bc6a92a7
2 changed files with 67 additions and 24 deletions
|
@ -1,6 +1,7 @@
|
||||||
#include "Painter.h"
|
#include "Painter.h"
|
||||||
#include "Font.h"
|
#include "Font.h"
|
||||||
#include "GraphicsBitmap.h"
|
#include "GraphicsBitmap.h"
|
||||||
|
#include "Emoji.h"
|
||||||
#include <AK/Assertions.h>
|
#include <AK/Assertions.h>
|
||||||
#include <AK/StdLibExtras.h>
|
#include <AK/StdLibExtras.h>
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
|
@ -8,6 +9,7 @@
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <AK/Utf8View.h>
|
||||||
|
|
||||||
#pragma GCC optimize("O3")
|
#pragma GCC optimize("O3")
|
||||||
|
|
||||||
|
@ -554,34 +556,69 @@ void Painter::draw_scaled_bitmap(const Rect& a_dst_rect, const GraphicsBitmap& s
|
||||||
draw_bitmap(point, font.glyph_bitmap(ch), color);
|
draw_bitmap(point, font.glyph_bitmap(ch), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Painter::draw_text_line(const Rect& a_rect, const StringView& text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
|
void Painter::draw_emoji(const Point& point, const Emoji& emoji, const Font& font)
|
||||||
|
{
|
||||||
|
if (!font.is_fixed_width())
|
||||||
|
blit(point, emoji.bitmap(), emoji.bitmap().rect());
|
||||||
|
else {
|
||||||
|
Rect dst_rect {
|
||||||
|
point.x(),
|
||||||
|
point.y(),
|
||||||
|
font.glyph_width('x'),
|
||||||
|
font.glyph_height()
|
||||||
|
};
|
||||||
|
draw_scaled_bitmap(dst_rect, emoji.bitmap(), emoji.bitmap().rect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Painter::draw_glyph_or_emoji(const Point& point, u32 codepoint, const Font& font, Color color)
|
||||||
|
{
|
||||||
|
if (codepoint < 256) {
|
||||||
|
// This looks like a regular character.
|
||||||
|
draw_glyph(point, (char)codepoint, font, color);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perhaps it's an emoji?
|
||||||
|
const Emoji* emoji = Emoji::emoji_for_codepoint(codepoint);
|
||||||
|
if (emoji == nullptr) {
|
||||||
|
dbg() << "Failed to find an emoji for codepoint " << codepoint;
|
||||||
|
draw_glyph(point, '?', font, color);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_emoji(point, *emoji, font);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Painter::draw_text_line(const Rect& a_rect, const Utf8View& text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
|
||||||
{
|
{
|
||||||
auto rect = a_rect;
|
auto rect = a_rect;
|
||||||
StringView final_text(text);
|
Utf8View final_text(text);
|
||||||
String elided_text;
|
String 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 new_length = 0;
|
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 (int i = 0; i < final_text.length(); ++i) {
|
for (auto it = final_text.begin(); it != final_text.end(); ++it) {
|
||||||
int glyph_width = font.glyph_width(final_text.characters_without_null_termination()[i]);
|
u32 codepoint = *it;
|
||||||
|
int glyph_width = font.glyph_or_emoji_width(codepoint);
|
||||||
// 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,
|
||||||
// we don't have to worry about spacing.
|
// we don't have to worry about spacing.
|
||||||
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;
|
||||||
++new_length;
|
byte_offset = final_text.byte_offset_of(it);
|
||||||
new_width += glyph_width + glyph_spacing;
|
new_width += glyph_width + glyph_spacing;
|
||||||
}
|
}
|
||||||
StringBuilder builder;
|
StringBuilder builder;
|
||||||
builder.append(StringView(final_text.characters_without_null_termination(), new_length));
|
builder.append(final_text.substring_view(0, byte_offset).as_string());
|
||||||
builder.append("...");
|
builder.append("...");
|
||||||
elided_text = builder.to_string();
|
elided_text = builder.to_string();
|
||||||
final_text = elided_text;
|
final_text = Utf8View { elided_text };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -604,14 +641,13 @@ void Painter::draw_text_line(const Rect& a_rect, const StringView& text, const F
|
||||||
auto point = rect.location();
|
auto point = rect.location();
|
||||||
int space_width = font.glyph_width(' ') + font.glyph_spacing();
|
int space_width = font.glyph_width(' ') + font.glyph_spacing();
|
||||||
|
|
||||||
for (ssize_t i = 0; i < final_text.length(); ++i) {
|
for (u32 codepoint : final_text) {
|
||||||
char ch = final_text.characters_without_null_termination()[i];
|
if (codepoint == ' ') {
|
||||||
if (ch == ' ') {
|
|
||||||
point.move_by(space_width, 0);
|
point.move_by(space_width, 0);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
draw_glyph(point, ch, font, color);
|
draw_glyph_or_emoji(point, codepoint, font, color);
|
||||||
point.move_by(font.glyph_width(ch) + font.glyph_spacing(), 0);
|
point.move_by(font.glyph_or_emoji_width(codepoint) + font.glyph_spacing(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,22 +656,25 @@ void Painter::draw_text(const Rect& rect, const StringView& text, TextAlignment
|
||||||
draw_text(rect, text, font(), alignment, color, elision);
|
draw_text(rect, text, font(), alignment, color, elision);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Painter::draw_text(const Rect& rect, const StringView& text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
|
void Painter::draw_text(const Rect& rect, const StringView& raw_text, const Font& font, TextAlignment alignment, Color color, TextElision elision)
|
||||||
{
|
{
|
||||||
Vector<StringView, 32> lines;
|
Utf8View text { raw_text };
|
||||||
|
Vector<Utf8View, 32> lines;
|
||||||
|
|
||||||
int start_of_current_line = 0;
|
int start_of_current_line = 0;
|
||||||
for (int i = 0; i < text.length(); ++i) {
|
for (auto it = text.begin(); it != text.end(); ++it) {
|
||||||
auto ch = text[i];
|
u32 codepoint = *it;
|
||||||
if (ch == '\n') {
|
if (codepoint == '\n') {
|
||||||
lines.append(text.substring_view(start_of_current_line, i - start_of_current_line));
|
int byte_offset = text.byte_offset_of(it);
|
||||||
start_of_current_line = i + 1;
|
Utf8View line = text.substring_view(start_of_current_line, byte_offset - start_of_current_line);
|
||||||
continue;
|
lines.append(line);
|
||||||
|
start_of_current_line = byte_offset + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start_of_current_line != text.length()) {
|
if (start_of_current_line != text.byte_length()) {
|
||||||
lines.append(text.substring_view(start_of_current_line, text.length() - start_of_current_line));
|
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;
|
static const int line_spacing = 4;
|
||||||
|
|
|
@ -7,11 +7,13 @@
|
||||||
#include <AK/AKString.h>
|
#include <AK/AKString.h>
|
||||||
#include <LibDraw/TextAlignment.h>
|
#include <LibDraw/TextAlignment.h>
|
||||||
#include <LibDraw/TextElision.h>
|
#include <LibDraw/TextElision.h>
|
||||||
|
#include <AK/Utf8View.h>
|
||||||
|
|
||||||
class CharacterBitmap;
|
class CharacterBitmap;
|
||||||
class GlyphBitmap;
|
class GlyphBitmap;
|
||||||
class GraphicsBitmap;
|
class GraphicsBitmap;
|
||||||
class Font;
|
class Font;
|
||||||
|
class Emoji;
|
||||||
|
|
||||||
class Painter {
|
class Painter {
|
||||||
public:
|
public:
|
||||||
|
@ -34,6 +36,8 @@ public:
|
||||||
void draw_text(const Rect&, const StringView&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
|
void draw_text(const Rect&, const StringView&, TextAlignment = TextAlignment::TopLeft, Color = Color::Black, TextElision = TextElision::None);
|
||||||
void draw_glyph(const Point&, char, Color);
|
void draw_glyph(const Point&, char, Color);
|
||||||
void draw_glyph(const Point&, char, const Font&, Color);
|
void draw_glyph(const Point&, char, const Font&, Color);
|
||||||
|
void draw_emoji(const Point&, const Emoji&, const Font&);
|
||||||
|
void draw_glyph_or_emoji(const Point&, u32 codepoint, const Font&, Color);
|
||||||
|
|
||||||
const Font& font() const { return *state().font; }
|
const Font& font() const { return *state().font; }
|
||||||
void set_font(const Font& font) { state().font = &font; }
|
void set_font(const Font& font) { state().font = &font; }
|
||||||
|
@ -70,7 +74,7 @@ protected:
|
||||||
void blit_with_opacity(const Point&, const GraphicsBitmap&, const Rect& src_rect, float opacity);
|
void blit_with_opacity(const Point&, const GraphicsBitmap&, const Rect& src_rect, float opacity);
|
||||||
void draw_pixel(const Point&, Color, int thickness = 1);
|
void draw_pixel(const Point&, Color, int thickness = 1);
|
||||||
|
|
||||||
void draw_text_line(const Rect&, const StringView&, const Font&, TextAlignment, Color, TextElision);
|
void draw_text_line(const Rect&, const Utf8View&, const Font&, TextAlignment, Color, TextElision);
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
const Font* font;
|
const Font* font;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue