|
@@ -12,26 +12,18 @@
|
|
|
|
|
|
#include "Painter.h"
|
|
#include "Painter.h"
|
|
#include "Bitmap.h"
|
|
#include "Bitmap.h"
|
|
-#include "Font/Emoji.h"
|
|
|
|
#include "Font/Font.h"
|
|
#include "Font/Font.h"
|
|
#include <AK/Assertions.h>
|
|
#include <AK/Assertions.h>
|
|
-#include <AK/Debug.h>
|
|
|
|
#include <AK/Function.h>
|
|
#include <AK/Function.h>
|
|
#include <AK/Math.h>
|
|
#include <AK/Math.h>
|
|
#include <AK/Memory.h>
|
|
#include <AK/Memory.h>
|
|
-#include <AK/Queue.h>
|
|
|
|
-#include <AK/QuickSort.h>
|
|
|
|
#include <AK/Stack.h>
|
|
#include <AK/Stack.h>
|
|
#include <AK/StdLibExtras.h>
|
|
#include <AK/StdLibExtras.h>
|
|
-#include <AK/StringBuilder.h>
|
|
|
|
#include <AK/Utf8View.h>
|
|
#include <AK/Utf8View.h>
|
|
#include <LibGfx/Palette.h>
|
|
#include <LibGfx/Palette.h>
|
|
#include <LibGfx/Path.h>
|
|
#include <LibGfx/Path.h>
|
|
#include <LibGfx/Quad.h>
|
|
#include <LibGfx/Quad.h>
|
|
-#include <LibGfx/TextDirection.h>
|
|
|
|
#include <LibGfx/TextLayout.h>
|
|
#include <LibGfx/TextLayout.h>
|
|
-#include <LibUnicode/CharacterTypes.h>
|
|
|
|
-#include <LibUnicode/Emoji.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdio.h>
|
|
|
|
|
|
#if defined(AK_COMPILER_GCC)
|
|
#if defined(AK_COMPILER_GCC)
|
|
@@ -882,303 +874,6 @@ void Painter::draw_emoji(IntPoint point, Gfx::Bitmap const& emoji, Font const& f
|
|
draw_scaled_bitmap(dst_rect, emoji, emoji.rect());
|
|
draw_scaled_bitmap(dst_rect, emoji, emoji.rect());
|
|
}
|
|
}
|
|
|
|
|
|
-void Painter::draw_glyph_or_emoji(FloatPoint point, Utf8CodePointIterator& it, Font const& font, Color color)
|
|
|
|
-{
|
|
|
|
- auto draw_glyph_or_emoji = prepare_draw_glyph_or_emoji(point, it, font);
|
|
|
|
- if (draw_glyph_or_emoji.has<DrawGlyph>()) {
|
|
|
|
- auto& glyph = draw_glyph_or_emoji.get<DrawGlyph>();
|
|
|
|
- draw_glyph(glyph.position, glyph.code_point, *glyph.font, color);
|
|
|
|
- } else {
|
|
|
|
- auto& emoji = draw_glyph_or_emoji.get<DrawEmoji>();
|
|
|
|
- draw_emoji(emoji.position.to_type<int>(), *emoji.emoji, *emoji.font);
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-template<typename DrawGlyphFunction>
|
|
|
|
-void draw_text_line(FloatRect const& a_rect, Utf8View const& text, Font const& font, TextAlignment alignment, TextDirection direction, DrawGlyphFunction draw_glyph)
|
|
|
|
-{
|
|
|
|
- auto rect = a_rect;
|
|
|
|
-
|
|
|
|
- switch (alignment) {
|
|
|
|
- case TextAlignment::TopLeft:
|
|
|
|
- case TextAlignment::CenterLeft:
|
|
|
|
- case TextAlignment::BottomLeft:
|
|
|
|
- break;
|
|
|
|
- case TextAlignment::TopRight:
|
|
|
|
- case TextAlignment::CenterRight:
|
|
|
|
- case TextAlignment::BottomRight:
|
|
|
|
- rect.set_x(rect.right() - 1 - font.width(text));
|
|
|
|
- break;
|
|
|
|
- case TextAlignment::TopCenter:
|
|
|
|
- case TextAlignment::BottomCenter:
|
|
|
|
- case TextAlignment::Center: {
|
|
|
|
- auto shrunken_rect = rect;
|
|
|
|
- shrunken_rect.set_width(font.width(text));
|
|
|
|
- shrunken_rect.center_within(rect);
|
|
|
|
- rect = shrunken_rect;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- default:
|
|
|
|
- VERIFY_NOT_REACHED();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- auto point = rect.location();
|
|
|
|
- auto space_width = font.glyph_width(' ');
|
|
|
|
-
|
|
|
|
- if (direction == TextDirection::RTL) {
|
|
|
|
- point.translate_by(rect.width(), 0); // Start drawing from the end
|
|
|
|
- space_width = -space_width; // Draw spaces backwards
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- u32 last_code_point { 0 };
|
|
|
|
- for (auto it = text.begin(); it != text.end(); ++it) {
|
|
|
|
- auto code_point = *it;
|
|
|
|
- if (should_paint_as_space(code_point)) {
|
|
|
|
- point.translate_by(space_width, 0);
|
|
|
|
- last_code_point = code_point;
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- auto kerning = font.glyphs_horizontal_kerning(last_code_point, code_point);
|
|
|
|
- if (kerning != 0.0f)
|
|
|
|
- point.translate_by(direction == TextDirection::LTR ? kerning : -kerning, 0);
|
|
|
|
-
|
|
|
|
- auto it_copy = it; // The callback function will advance the iterator, so create a copy for this lookup.
|
|
|
|
- FloatSize glyph_size(font.glyph_or_emoji_width(it_copy), font.pixel_size());
|
|
|
|
-
|
|
|
|
- if (direction == TextDirection::RTL)
|
|
|
|
- point.translate_by(-glyph_size.width(), 0); // If we are drawing right to left, we have to move backwards before drawing the glyph
|
|
|
|
- draw_glyph({ point, glyph_size }, it);
|
|
|
|
- if (direction == TextDirection::LTR)
|
|
|
|
- point.translate_by(glyph_size.width(), 0);
|
|
|
|
- // The callback function might have exhausted the iterator.
|
|
|
|
- if (it == text.end())
|
|
|
|
- break;
|
|
|
|
- last_code_point = code_point;
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-static inline size_t draw_text_get_length(Utf8View const& text)
|
|
|
|
-{
|
|
|
|
- return text.byte_length();
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-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.
|
|
|
|
-
|
|
|
|
- // FIXME: Support HTML "dir" attribute (how?)
|
|
|
|
- u8 paragraph_embedding_level = initial_direction == TextDirection::LTR ? 0 : 1;
|
|
|
|
- Vector<u8> embedding_levels;
|
|
|
|
- embedding_levels.ensure_capacity(text.length());
|
|
|
|
- for (size_t i = 0; i < text.length(); i++)
|
|
|
|
- embedding_levels.unchecked_append(paragraph_embedding_level);
|
|
|
|
-
|
|
|
|
- // FIXME: Support Explicit Directional Formatting Characters
|
|
|
|
-
|
|
|
|
- Vector<BidirectionalClass> character_classes;
|
|
|
|
- character_classes.ensure_capacity(text.length());
|
|
|
|
- for (u32 code_point : text)
|
|
|
|
- character_classes.unchecked_append(get_char_bidi_class(code_point));
|
|
|
|
-
|
|
|
|
- // resolving weak types
|
|
|
|
- BidirectionalClass paragraph_class = initial_direction == TextDirection::LTR ? BidirectionalClass::STRONG_LTR : BidirectionalClass::STRONG_RTL;
|
|
|
|
- for (size_t i = 0; i < character_classes.size(); i++) {
|
|
|
|
- if (character_classes[i] != BidirectionalClass::WEAK_SEPARATORS)
|
|
|
|
- continue;
|
|
|
|
- for (ssize_t j = i - 1; j >= 0; j--) {
|
|
|
|
- auto character_class = character_classes[j];
|
|
|
|
- if (character_class != BidirectionalClass::STRONG_RTL && character_class != BidirectionalClass::STRONG_LTR)
|
|
|
|
- continue;
|
|
|
|
- character_classes[i] = character_class;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- if (character_classes[i] == BidirectionalClass::WEAK_SEPARATORS)
|
|
|
|
- character_classes[i] = paragraph_class;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // resolving neutral types
|
|
|
|
- auto left_side = BidirectionalClass::NEUTRAL;
|
|
|
|
- auto sequence_length = 0;
|
|
|
|
- for (size_t i = 0; i < character_classes.size(); i++) {
|
|
|
|
- auto character_class = character_classes[i];
|
|
|
|
- if (left_side == BidirectionalClass::NEUTRAL) {
|
|
|
|
- if (character_class != BidirectionalClass::NEUTRAL)
|
|
|
|
- left_side = character_class;
|
|
|
|
- else
|
|
|
|
- character_classes[i] = paragraph_class;
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
- if (character_class != BidirectionalClass::NEUTRAL) {
|
|
|
|
- BidirectionalClass sequence_class;
|
|
|
|
- if (bidi_class_to_direction(left_side) == bidi_class_to_direction(character_class)) {
|
|
|
|
- sequence_class = left_side == BidirectionalClass::STRONG_RTL ? BidirectionalClass::STRONG_RTL : BidirectionalClass::STRONG_LTR;
|
|
|
|
- } else {
|
|
|
|
- sequence_class = paragraph_class;
|
|
|
|
- }
|
|
|
|
- for (auto j = 0; j < sequence_length; j++) {
|
|
|
|
- character_classes[i - j - 1] = sequence_class;
|
|
|
|
- }
|
|
|
|
- sequence_length = 0;
|
|
|
|
- left_side = character_class;
|
|
|
|
- } else {
|
|
|
|
- sequence_length++;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- for (auto i = 0; i < sequence_length; i++)
|
|
|
|
- character_classes[character_classes.size() - i - 1] = paragraph_class;
|
|
|
|
-
|
|
|
|
- // resolving implicit levels
|
|
|
|
- for (size_t i = 0; i < character_classes.size(); i++) {
|
|
|
|
- auto character_class = character_classes[i];
|
|
|
|
- if ((embedding_levels[i] % 2) == 0) {
|
|
|
|
- if (character_class == BidirectionalClass::STRONG_RTL)
|
|
|
|
- embedding_levels[i] += 1;
|
|
|
|
- else if (character_class == BidirectionalClass::WEAK_NUMBERS || character_class == BidirectionalClass::WEAK_SEPARATORS)
|
|
|
|
- embedding_levels[i] += 2;
|
|
|
|
- } else {
|
|
|
|
- if (character_class == BidirectionalClass::STRONG_LTR || character_class == BidirectionalClass::WEAK_NUMBERS || character_class == BidirectionalClass::WEAK_SEPARATORS)
|
|
|
|
- embedding_levels[i] += 1;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // splitting into runs
|
|
|
|
- auto run_code_points_start = text.begin();
|
|
|
|
- auto next_code_points_slice = [&](auto length) {
|
|
|
|
- Vector<u32> run_code_points;
|
|
|
|
- run_code_points.ensure_capacity(length);
|
|
|
|
- for (size_t j = 0; j < length; ++j, ++run_code_points_start)
|
|
|
|
- run_code_points.unchecked_append(*run_code_points_start);
|
|
|
|
- return run_code_points;
|
|
|
|
- };
|
|
|
|
- Vector<DirectionalRun> runs;
|
|
|
|
- size_t start = 0;
|
|
|
|
- u8 level = embedding_levels[0];
|
|
|
|
- for (size_t i = 1; i < embedding_levels.size(); ++i) {
|
|
|
|
- if (embedding_levels[i] == level)
|
|
|
|
- continue;
|
|
|
|
- auto code_points_slice = next_code_points_slice(i - start);
|
|
|
|
- runs.append({ move(code_points_slice), level });
|
|
|
|
- start = i;
|
|
|
|
- level = embedding_levels[i];
|
|
|
|
- }
|
|
|
|
- auto code_points_slice = next_code_points_slice(embedding_levels.size() - start);
|
|
|
|
- runs.append({ move(code_points_slice), level });
|
|
|
|
-
|
|
|
|
- // reordering resolved levels
|
|
|
|
- // FIXME: missing special cases for trailing whitespace characters
|
|
|
|
- u8 minimum_level = 128;
|
|
|
|
- u8 maximum_level = 0;
|
|
|
|
- for (auto& run : runs) {
|
|
|
|
- minimum_level = min(minimum_level, run.embedding_level());
|
|
|
|
- maximum_level = max(minimum_level, run.embedding_level());
|
|
|
|
- }
|
|
|
|
- if ((minimum_level % 2) == 0)
|
|
|
|
- minimum_level++;
|
|
|
|
- auto runs_count = runs.size() - 1;
|
|
|
|
- while (maximum_level <= minimum_level) {
|
|
|
|
- size_t run_index = 0;
|
|
|
|
- while (run_index < runs_count) {
|
|
|
|
- while (run_index < runs_count && runs[run_index].embedding_level() < maximum_level)
|
|
|
|
- run_index++;
|
|
|
|
- auto reverse_start = run_index;
|
|
|
|
- while (run_index <= runs_count && runs[run_index].embedding_level() >= maximum_level)
|
|
|
|
- run_index++;
|
|
|
|
- auto reverse_end = run_index - 1;
|
|
|
|
- while (reverse_start < reverse_end) {
|
|
|
|
- swap(runs[reverse_start], runs[reverse_end]);
|
|
|
|
- reverse_start++;
|
|
|
|
- reverse_end--;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- maximum_level--;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // mirroring RTL mirror characters
|
|
|
|
- for (auto& run : runs) {
|
|
|
|
- if (run.direction() == TextDirection::LTR)
|
|
|
|
- continue;
|
|
|
|
- for (auto& code_point : run.code_points()) {
|
|
|
|
- code_point = get_mirror_char(code_point);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return runs;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-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);
|
|
|
|
- if (char_class == BidirectionalClass::NEUTRAL)
|
|
|
|
- continue;
|
|
|
|
- if (bidi_class_to_direction(char_class) != initial_direction)
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
- return false;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-template<typename DrawGlyphFunction>
|
|
|
|
-void Painter::do_draw_text(FloatRect 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;
|
|
|
|
-
|
|
|
|
- TextLayout layout(font, text, rect);
|
|
|
|
-
|
|
|
|
- auto line_height = font.preferred_line_height();
|
|
|
|
-
|
|
|
|
- auto lines = layout.lines(elision, wrapping);
|
|
|
|
- auto bounding_rect = layout.bounding_rect(wrapping);
|
|
|
|
-
|
|
|
|
- bounding_rect.align_within(rect, alignment);
|
|
|
|
-
|
|
|
|
- for (size_t i = 0; i < lines.size(); ++i) {
|
|
|
|
- auto line = Utf8View { lines[i] };
|
|
|
|
-
|
|
|
|
- FloatRect line_rect { bounding_rect.x(), bounding_rect.y() + i * line_height, bounding_rect.width(), line_height };
|
|
|
|
-
|
|
|
|
- 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);
|
|
|
|
- auto current_dx = line_direction == TextDirection::LTR ? 0 : line_rect.width();
|
|
|
|
- for (auto& directional_run : directional_runs) {
|
|
|
|
- // NOTE: DirectionalRun returns Utf32View which isn't
|
|
|
|
- // compatible with draw_text_line.
|
|
|
|
- StringBuilder builder;
|
|
|
|
- builder.append(directional_run.text());
|
|
|
|
- auto line_text = Utf8View { builder.string_view() };
|
|
|
|
-
|
|
|
|
- auto run_width = font.width(line_text);
|
|
|
|
- if (line_direction == TextDirection::RTL)
|
|
|
|
- current_dx -= run_width;
|
|
|
|
- auto run_rect = line_rect.translated(current_dx, 0);
|
|
|
|
- run_rect.set_width(run_width);
|
|
|
|
-
|
|
|
|
- draw_text_line(run_rect, line_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, line_direction, draw_glyph);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-void Painter::draw_text(FloatRect const& rect, StringView raw_text, Font const& font, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
|
|
|
|
-{
|
|
|
|
- Utf8View text { raw_text };
|
|
|
|
- do_draw_text(rect, text, font, alignment, elision, wrapping, [&](FloatRect const& r, Utf8CodePointIterator& it) {
|
|
|
|
- draw_glyph_or_emoji(r.location(), it, font, color);
|
|
|
|
- });
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-void Painter::draw_text(IntRect const& rect, StringView raw_text, Font const& font, TextAlignment alignment, Color color, TextElision elision, TextWrapping wrapping)
|
|
|
|
-{
|
|
|
|
- draw_text(rect.to_type<float>(), raw_text, font, alignment, color, elision, wrapping);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
void Painter::set_pixel(IntPoint p, Color color, bool blend)
|
|
void Painter::set_pixel(IntPoint p, Color color, bool blend)
|
|
{
|
|
{
|
|
auto point = p;
|
|
auto point = p;
|