Serenity Operating System
at master 152 lines 5.3 kB view raw
1/* 2 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Utf8View.h> 8#include <LibWeb/DOM/Range.h> 9#include <LibWeb/Layout/LayoutState.h> 10#include <LibWeb/Layout/LineBoxFragment.h> 11#include <LibWeb/Layout/TextNode.h> 12#include <LibWeb/Layout/Viewport.h> 13#include <ctype.h> 14 15namespace Web::Layout { 16 17bool LineBoxFragment::ends_in_whitespace() const 18{ 19 auto text = this->text(); 20 if (text.is_empty()) 21 return false; 22 return isspace(text[text.length() - 1]); 23} 24 25bool LineBoxFragment::is_justifiable_whitespace() const 26{ 27 return text() == " "; 28} 29 30StringView LineBoxFragment::text() const 31{ 32 if (!is<TextNode>(layout_node())) 33 return {}; 34 return verify_cast<TextNode>(layout_node()).text_for_rendering().substring_view(m_start, m_length); 35} 36 37CSSPixelRect const LineBoxFragment::absolute_rect() const 38{ 39 CSSPixelRect rect { {}, size() }; 40 rect.set_location(m_layout_node.containing_block()->paint_box()->absolute_position()); 41 rect.translate_by(offset()); 42 return rect; 43} 44 45int LineBoxFragment::text_index_at(CSSPixels x) const 46{ 47 if (!is<TextNode>(layout_node())) 48 return 0; 49 auto& layout_text = verify_cast<TextNode>(layout_node()); 50 auto& font = layout_text.font(); 51 Utf8View view(text()); 52 53 CSSPixels relative_x = x - absolute_x(); 54 float glyph_spacing = font.glyph_spacing(); 55 56 if (relative_x < 0) 57 return 0; 58 59 CSSPixels width_so_far = 0; 60 for (auto it = view.begin(); it != view.end(); ++it) { 61 auto previous_it = it; 62 CSSPixels glyph_width = font.glyph_or_emoji_width(it); 63 64 if ((width_so_far + (glyph_width + glyph_spacing) / 2) > relative_x) 65 return m_start + view.byte_offset_of(previous_it); 66 67 width_so_far += glyph_width + glyph_spacing; 68 } 69 70 return m_start + m_length; 71} 72 73CSSPixelRect LineBoxFragment::selection_rect(Gfx::Font const& font) const 74{ 75 if (layout_node().selection_state() == Node::SelectionState::None) 76 return {}; 77 78 if (layout_node().selection_state() == Node::SelectionState::Full) 79 return absolute_rect(); 80 81 if (!is<TextNode>(layout_node())) 82 return {}; 83 84 auto selection = layout_node().root().selection(); 85 if (!selection) 86 return {}; 87 auto range = selection->range(); 88 if (!range) 89 return {}; 90 91 // FIXME: m_start and m_length should be unsigned and then we won't need these casts. 92 auto const start_index = static_cast<unsigned>(m_start); 93 auto const end_index = static_cast<unsigned>(m_start) + static_cast<unsigned>(m_length); 94 auto text = this->text(); 95 96 if (layout_node().selection_state() == Node::SelectionState::StartAndEnd) { 97 // we are in the start/end node (both the same) 98 if (start_index > range->end_offset()) 99 return {}; 100 if (end_index < range->start_offset()) 101 return {}; 102 103 if (range->start_offset() == range->end_offset()) 104 return {}; 105 106 auto selection_start_in_this_fragment = max(0, range->start_offset() - m_start); 107 auto selection_end_in_this_fragment = min(m_length, range->end_offset() - m_start); 108 auto pixel_distance_to_first_selected_character = font.width(text.substring_view(0, selection_start_in_this_fragment)); 109 auto pixel_width_of_selection = font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment)) + 1; 110 111 auto rect = absolute_rect(); 112 rect.set_x(rect.x() + pixel_distance_to_first_selected_character); 113 rect.set_width(pixel_width_of_selection); 114 115 return rect; 116 } 117 if (layout_node().selection_state() == Node::SelectionState::Start) { 118 // we are in the start node 119 if (end_index < range->start_offset()) 120 return {}; 121 122 auto selection_start_in_this_fragment = max(0, range->start_offset() - m_start); 123 auto selection_end_in_this_fragment = m_length; 124 auto pixel_distance_to_first_selected_character = font.width(text.substring_view(0, selection_start_in_this_fragment)); 125 auto pixel_width_of_selection = font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment)) + 1; 126 127 auto rect = absolute_rect(); 128 rect.set_x(rect.x() + pixel_distance_to_first_selected_character); 129 rect.set_width(pixel_width_of_selection); 130 131 return rect; 132 } 133 if (layout_node().selection_state() == Node::SelectionState::End) { 134 // we are in the end node 135 if (start_index > range->end_offset()) 136 return {}; 137 138 auto selection_start_in_this_fragment = 0; 139 auto selection_end_in_this_fragment = min(range->end_offset() - m_start, m_length); 140 auto pixel_distance_to_first_selected_character = font.width(text.substring_view(0, selection_start_in_this_fragment)); 141 auto pixel_width_of_selection = font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment)) + 1; 142 143 auto rect = absolute_rect(); 144 rect.set_x(rect.x() + pixel_distance_to_first_selected_character); 145 rect.set_width(pixel_width_of_selection); 146 147 return rect; 148 } 149 return {}; 150} 151 152}