LineBoxFragment.cpp 5.4 KB

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