Serenity Operating System
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}