/* * Copyright (c) 2024, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include namespace Web::Painting { PaintableFragment::PaintableFragment(Layout::LineBoxFragment const& fragment) : m_layout_node(fragment.layout_node()) , m_offset(fragment.offset()) , m_size(fragment.size()) , m_baseline(fragment.baseline()) , m_start(fragment.start()) , m_length(fragment.length()) , m_glyph_run(fragment.glyph_run()) , m_writing_mode(fragment.writing_mode()) { } CSSPixelRect const PaintableFragment::absolute_rect() const { CSSPixelRect rect { {}, size() }; auto const* containing_block = paintable().containing_block(); if (containing_block) rect.set_location(containing_block->absolute_position()); rect.translate_by(offset()); return rect; } int PaintableFragment::text_index_at(CSSPixelPoint position) const { if (!is(paintable())) return 0; CSSPixels relative_inline_offset = [&]() { switch (orientation()) { case Gfx::Orientation::Horizontal: return position.x() - absolute_rect().x(); case Gfx::Orientation::Vertical: return position.y() - absolute_rect().y(); default: VERIFY_NOT_REACHED(); } }(); if (relative_inline_offset < 0) return 0; auto const& glyphs = m_glyph_run->glyphs(); for (size_t i = 0; i < glyphs.size(); ++i) { auto glyph_position = CSSPixels::nearest_value_for(glyphs[i].position.x()); if (i + 1 < glyphs.size()) { auto next_glyph_position = CSSPixels::nearest_value_for(glyphs[i + 1].position.x()); if (relative_inline_offset >= glyph_position && relative_inline_offset < next_glyph_position) return m_start + i; } else { if (relative_inline_offset >= glyph_position) return m_start + i; } } return m_start + m_length; } CSSPixelRect PaintableFragment::range_rect(size_t start_offset, size_t end_offset) const { if (paintable().selection_state() == Paintable::SelectionState::None) return {}; if (paintable().selection_state() == Paintable::SelectionState::Full) return absolute_rect(); // FIXME: m_start and m_length should be unsigned and then we won't need these casts. auto const start_index = static_cast(m_start); auto const end_index = static_cast(m_start) + static_cast(m_length); auto const& font = glyph_run() ? glyph_run()->font() : layout_node().first_available_font(); auto text = string_view(); if (paintable().selection_state() == Paintable::SelectionState::StartAndEnd) { // we are in the start/end node (both the same) if (start_index > end_offset) return {}; if (end_index < start_offset) return {}; if (start_offset == end_offset) return {}; auto selection_start_in_this_fragment = max(0, start_offset - m_start); auto selection_end_in_this_fragment = min(m_length, end_offset - m_start); auto pixel_distance_to_first_selected_character = CSSPixels::nearest_value_for(font.width(text.substring_view(0, selection_start_in_this_fragment))); auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1; auto rect = absolute_rect(); switch (orientation()) { case Gfx::Orientation::Horizontal: rect.set_x(rect.x() + pixel_distance_to_first_selected_character); rect.set_width(pixel_width_of_selection); break; case Gfx::Orientation::Vertical: rect.set_y(rect.y() + pixel_distance_to_first_selected_character); rect.set_height(pixel_width_of_selection); break; default: VERIFY_NOT_REACHED(); } return rect; } if (paintable().selection_state() == Paintable::SelectionState::Start) { // we are in the start node if (end_index < start_offset) return {}; auto selection_start_in_this_fragment = max(0, start_offset - m_start); auto selection_end_in_this_fragment = m_length; auto pixel_distance_to_first_selected_character = CSSPixels::nearest_value_for(font.width(text.substring_view(0, selection_start_in_this_fragment))); auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1; auto rect = absolute_rect(); switch (orientation()) { case Gfx::Orientation::Horizontal: rect.set_x(rect.x() + pixel_distance_to_first_selected_character); rect.set_width(pixel_width_of_selection); break; case Gfx::Orientation::Vertical: rect.set_y(rect.y() + pixel_distance_to_first_selected_character); rect.set_height(pixel_width_of_selection); break; default: VERIFY_NOT_REACHED(); } return rect; } if (paintable().selection_state() == Paintable::SelectionState::End) { // we are in the end node if (start_index > end_offset) return {}; auto selection_start_in_this_fragment = 0; auto selection_end_in_this_fragment = min(end_offset - m_start, m_length); auto pixel_distance_to_first_selected_character = CSSPixels::nearest_value_for(font.width(text.substring_view(0, selection_start_in_this_fragment))); auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1; auto rect = absolute_rect(); switch (orientation()) { case Gfx::Orientation::Horizontal: rect.set_x(rect.x() + pixel_distance_to_first_selected_character); rect.set_width(pixel_width_of_selection); break; case Gfx::Orientation::Vertical: rect.set_y(rect.y() + pixel_distance_to_first_selected_character); rect.set_height(pixel_width_of_selection); break; default: VERIFY_NOT_REACHED(); } return rect; } return {}; } Gfx::Orientation PaintableFragment::orientation() const { switch (m_writing_mode) { case CSS::WritingMode::HorizontalTb: return Gfx::Orientation::Horizontal; case CSS::WritingMode::VerticalRl: case CSS::WritingMode::VerticalLr: case CSS::WritingMode::SidewaysRl: case CSS::WritingMode::SidewaysLr: return Gfx::Orientation::Vertical; default: VERIFY_NOT_REACHED(); } } CSSPixelRect PaintableFragment::selection_rect() const { if (auto const* focused_element = paintable().document().focused_element(); focused_element && is(*focused_element)) { HTML::FormAssociatedTextControlElement const* text_control_element = nullptr; if (is(*focused_element)) { text_control_element = static_cast(focused_element); } else if (is(*focused_element)) { text_control_element = static_cast(focused_element); } else { VERIFY_NOT_REACHED(); } auto selection_start = text_control_element->selection_start(); auto selection_end = text_control_element->selection_end(); return range_rect(selection_start, selection_end); } auto selection = paintable().document().get_selection(); if (!selection) return {}; auto range = selection->range(); if (!range) return {}; return range_rect(range->start_offset(), range->end_offset()); } StringView PaintableFragment::string_view() const { if (!is(paintable())) return {}; return static_cast(paintable()).text_for_rendering().bytes_as_string_view().substring_view(m_start, m_length); } }